package com.hyt.it.ogt.kq.service.gov.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.beust.jcommander.internal.Lists;
import com.google.common.base.Joiner;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.base.BaseEntity;
import com.hyt.core.base.BaseServiceImpl;
import com.hyt.core.constant.CommonConstant;
import com.hyt.core.util.UUIDUtils;
import com.hyt.exception.exception.BusinessLogicException;
import com.hyt.it.ogt.kq.common.bm.enums.CertificateTypeEnum;
import com.hyt.it.ogt.kq.common.bm.utils.Constant;
import com.hyt.it.ogt.kq.common.bm.utils.IDCardUtil;
import com.hyt.it.ogt.kq.common.config.ConfigManager;
import com.hyt.it.ogt.kq.common.config.exception.KqException;
import com.hyt.it.ogt.kq.common.exception.DataBeingLockedException;
import com.hyt.it.ogt.kq.common.exception.GovInvalidRequestException;
import com.hyt.it.ogt.kq.common.gov.constant.CodeEnum;
import com.hyt.it.ogt.kq.common.gov.constant.ResponseCode;
import com.hyt.it.ogt.kq.common.gov.constant.TaskExamTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.AnswerTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.IdentityTypeEnum;
import com.hyt.it.ogt.kq.common.gov.enums.SexEnums;
import com.hyt.it.ogt.kq.common.gov.enums.TaskCandidateAreaVirtualAreaIdEnums;
import com.hyt.it.ogt.kq.common.gov.utils.DateTimeUtil;
import com.hyt.it.ogt.kq.common.gov.utils.StringUtil;
import com.hyt.it.ogt.kq.common.gov.utils.TextUtil;
import com.hyt.it.ogt.kq.service.gov.den.TencentCloudCosUtil;
import com.hyt.it.ogt.kq.service.gov.feign.ks.KsClient;
import com.hyt.it.ogt.kq.service.gov.feign.ks.model.PushTimeRoomCandidateVO;
import com.hyt.it.ogt.kq.service.gov.feign.pt.GovPtClient;
import com.hyt.it.ogt.kq.service.gov.feign.tps.GovTpsClient;
import com.hyt.it.ogt.kq.service.gov.mapper.CandidateMapper;
import com.hyt.it.ogt.kq.service.gov.mapper.CandidateSubjectMapper;
import com.hyt.it.ogt.kq.service.gov.model.dto.*;
import com.hyt.it.ogt.kq.service.gov.model.dto.arrange.*;
import com.hyt.it.ogt.kq.service.gov.model.dto.ticket.CandidateInfoDTO;
import com.hyt.it.ogt.kq.service.gov.model.dto.ticket.TimeInfoDTO;
import com.hyt.it.ogt.kq.service.gov.model.entity.*;
import com.hyt.it.ogt.kq.service.gov.model.excel.*;
import com.hyt.it.ogt.kq.service.gov.model.excel.listener.SelfLevelListener;
import com.hyt.it.ogt.kq.service.gov.model.excel.listener.TaskCandidateListener;
import com.hyt.it.ogt.kq.service.gov.model.param.*;
import com.hyt.it.ogt.kq.service.gov.model.vo.BucketVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidateBaseInfoVO;
import com.hyt.it.ogt.kq.service.gov.model.vo.CandidatePageVO;
import com.hyt.it.ogt.kq.service.gov.photo.PhotoTypeEnum;
import com.hyt.it.ogt.kq.service.gov.service.*;
import com.hyt.it.ogt.kq.service.gov.service.obs.CloudObsService;
import com.hyt.it.ogt.kq.service.model.entity.*;
import com.hyt.loginfo.model.Organization;
import com.hyt.loginfo.model.User;
import com.hyt.loginfo.service.IUaDeptService;
import com.hyt.model.tps.vo.Bucket;
import com.hyt.progress.constants.ProgressConstant;
import com.hyt.progress.service.ProgressManager;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.net.URLEncoder;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.seata.common.util.LambdaUtils.distinctByKey;


/**
 * <p>
 * 考生表 服务实现类
 * </p>
 *
 * @author huangyh
 * @since 2021-03-08
 */
@Slf4j
@Service
public class CandidateServiceImpl extends BaseServiceImpl<CandidateMapper, Candidate> implements ICandidateService {

    @Autowired
    private ICandidatePhotoService iCandidatePhotoService;

    @Autowired
    private ISubjectService iSubjectService;

    @Autowired
    private ICandidateSubjectService iCandidateSubjectService;

    @Autowired
    private ICandidateService iCandidateService;

    @Resource
    private CandidateMapper candidateMapper;
    @Resource
    private CloudObsService cloudObsService;
    @Resource
    private ITaskCandidateAreaService iTaskCandidateAreaService;

    @Resource
    private GovIDictService iDictService;

    @Resource
    private ITaskService iTaskService;

    @Resource
    private ITimeRoomCandidateService iTimeRoomCandidateService;

    @Resource
    private ICustomArrangeService iCustomArrangeService;

    @Resource
    private ITimeSubjectService iTimeSubjectService;

    @Resource
    private ConfigManager configManager;

    @Resource
    private GovTpsClient tpsClient;

    @Resource
    private KsClient ksClient;

    @Resource
    private GovPtClient govPtClient;

    @Resource
    private IUaDeptService iUaDeptService;

    @Resource
    private ITaskRoomService iTaskRoomService;

    @Resource
    private CandidateSubjectMapper candidateSubjectMapper;

    @Resource
    private IOfficeAreaService iOfficeAreaService;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(CandidateParam candidateParam, User user) throws KqException {

        Task task = iTaskService.getTaskById(candidateParam.getTaskId());

        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowException(task);
        //: 如果任务绑定报名，则不允许添加考生
        iTaskService.checkBindBm(candidateParam.getTaskId());

        //校验考生信息
        this.verifyCandidateInfo(candidateParam);

        //1.如果客户端有更新见证号码 则判断是否重复
        List<Candidate> findByIdNumber = iCandidateService.lambdaQuery()
                .eq(Candidate::getIdNumber, candidateParam.getIdNumber())
                .eq(Candidate::getDelFlag, false)
                .eq(Candidate::getTaskId, candidateParam.getTaskId())
                .list();
        //2.证件号重复,不允许再添加考生
        if (TextUtil.isNotNull(findByIdNumber)) {
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_ID_NUMBER_EXISTENT.getCode(), ResponseCode.ERROR_UPDATE_CANDIDATE_ID_NUMBER_EXISTENT.getMsg());
        }

        //任务报名字段默认为0，表示导入模式
        if (StringUtils.isBlank(task.getTaskBmId())) {
            iTaskService.updateTaskBmId(candidateParam.getTaskId(), String.valueOf(CodeEnum.TASK_DATA_IMPORT_VALUE.getCode()));
        }

        //生成考生id
        String candidateId = UUIDUtils.newSortUUID();
        candidateParam.setId(candidateId);

        //保存考生照片数据
        if (StringUtils.isNotBlank(candidateParam.getPhotoPath())) {
            //用户上传了考生照片则插入考生照片表，反之则不插入
            CandidatePhoto candidatePhoto = new CandidatePhoto();
            candidatePhoto.setCandidateId(candidateId)
                    .setPhotoType(PhotoTypeEnum.PERSONAL.getCode())
                    .setPhotoPath(candidateParam.getPhotoPath())
                    .setName(candidateParam.getPhotoName())
                    .setTaskId(candidateParam.getTaskId());
            //插入考生照片表
            if (!iCandidatePhotoService.save(candidatePhoto)) {
                throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getCode(), ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getMsg());
            }
        }

        //设置报名信息字段
        Candidate candidate = new Candidate();
        BeanUtils.copyProperties(candidateParam, candidate);
        String taskId = candidateParam.getTaskId();
        candidate.setTaskId(taskId);
        candidate.setImportType(0);
        //candidate.setDeptId(user.getOrganizationid());
        candidate.setOfficeId(user.getOfficeid());

        //插入考生表
        if (!save(candidate)) {
            //插入考生表失败
            throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getCode(), ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getMsg());
        }

        //处理考生科目数据
        List<CandidateSubjectParam> subjects = candidateParam.getSubjects();
        List<CandidateSubject> candidateSubjectList = subjects.stream().map(subject -> {
            CandidateSubject candidateSubject = new CandidateSubject();
            BeanUtils.copyProperties(subject, candidateSubject);
            candidateSubject.setLanguage(1);
            candidateSubject.setTaskId(taskId)
                    .setCandidateId(candidateId);
            candidateSubject.setLanguage(1);
            return candidateSubject;
        }).collect(Collectors.toList());
        //插入考生科目中间表
        if (!iCandidateSubjectService.saveBatch(candidateSubjectList)) {
            throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_SUBJECT_FAIL.getCode(), ResponseCode.ERROR_ADD_CANDIDATE_INSERT_SUBJECT_FAIL.getMsg());
        }
    }

    /**
     * 校验考生信息
     * @param candidateParam 添加考生信息
     */
    private void verifyCandidateInfo(CandidateParam candidateParam) {
        //校验出生日期
        if (StringUtils.isNotBlank(candidateParam.getBirthday())) {
            if (!DateTimeUtil.isBirthday(candidateParam.getBirthday())) {
                //出生日期格式不合法
                throw new KqException(ResponseCode.ADD_CANDIDATE_INVALID_BIRTHDAY.getCode(),
                        ResponseCode.ADD_CANDIDATE_INVALID_BIRTHDAY.getMsg());
            }
        }
        //如果是大陆身份证，则验证出生日期和身份证是否一致
       /* if (StringUtils.isNotBlank(candidateParam.getBirthday()) && candidateParam.getIdType() == 0) {
            String card = candidateParam.getBirthday().replaceAll("-","")
                    .replaceAll("00:00:00","")
                    .replaceAll(" ","");
            log.info("出生日期，{},{}",card,candidateParam.getBirthday());
            if(!candidateParam.getIdNumber().contains(card)) {
                throw new KwGovException(ResponseCodeOld.ERROR_ID_NUMBER_BIRTHDAY_NOT,
                        "身份证和出生日期不匹配，请填写正确的信息。");
            }
        }*/
        //校验邮箱
        if (StringUtils.isNotBlank(candidateParam.getEmail())) {
            if (!Validator.isEmail(candidateParam.getEmail())) {
                //邮箱格式不合法
                throw new KqException(ResponseCode.ADD_CANDIDATE_INVALID_EMAIL.getCode(),
                        ResponseCode.ADD_CANDIDATE_INVALID_EMAIL.getMsg());
            }
        }
        //校验手机号
        if (StringUtils.isNotBlank(candidateParam.getPhone())) {
            String phone = candidateParam.getPhone();
            boolean validPhone = StringUtil.isMobile(phone);
            if (!validPhone) {
                //手机号格式不合法
                throw new KqException(ResponseCode.ADD_CANDIDATE_INVALID_PHONE.getCode(),
                        ResponseCode.ADD_CANDIDATE_INVALID_PHONE.getMsg());
            }
        }

        // 报名和外部导入考生，要求如果区域有值，必须是3级的，手动新增也维持同样逻辑，校验下数据
        if(StringUtils.isNotBlank(candidateParam.getProvince()) ) {
            if(StringUtils.isBlank(candidateParam.getCity()) || StringUtils.isBlank(candidateParam.getDistrict())) {
                throw new KqException(ResponseCode.ADD_CANDIDATE_NOT_THREE_AREA.getCode(), ResponseCode.ADD_CANDIDATE_NOT_THREE_AREA.getMsg());
            }
        }
    }

//    /**
//     * 校验报考科目合理性
//     * @param subjects 考生保考的科目
//     * @param idCodeList 当前任务下科目的id与编号的对应关系
//     * @throws KwGovException 报考科目合理性校验不通过，抛出异常
//     */
////    private void verifySubject(List<CandidateSubjectParam> subjects, List<Subject> idCodeList) throws KwGovException {
////        if (CollectionUtils.isEmpty(idCodeList)) {
////            //当前任务下暂无科目数据
////            throw new KwGovException(ResponseCodeOld.ERROR_UPDATE_CANDIDATE_CURRENT_TASK_NO_SUBJECT, "当前任务下暂无科目数据");
////        }
////        subjects.forEach(subject -> {
////            if (idCodeList.stream().noneMatch(idCode -> idCode.getId().equals(subject.getSubjectId()))) {
////                //考生报考的科目中有已不存在的科目
////                throw new KwGovException(ResponseCodeOld.ERROR_UPDATE_CANDIDATE_SUBJECT_NON_EXISTENT, "考生报考的科目中有已不存在的科目");
////            }
////        });
////    }

    @Override
    public Page<CandidatePageVO> pageOfCandidate(CandidatePageParam param) {
        // 查询分页数据
        Page<CandidatePageParam> pageParam = new Page<>();
        pageParam.setSize(param.getSize());
        pageParam.setCurrent(param.getCurrent());
        pageParam.setOrders(param.getOrders());
        if(StrUtil.isNotBlank(param.getSubjectId())){
            String[] subjects = param.getSubjectId().split(",");
            List<String> subjectList = new ArrayList<>();
            subjectList = Arrays.asList(subjects);
            param.setSubjectIds(subjectList);
        }
        Page<CandidatePageVO> page = candidateMapper.page(pageParam,param);
        List<CandidatePageVO> records = page.getRecords();
        if (records.isEmpty()) {
            return page;
        }

        // 查询具体学生的科目信息
        String taskId = param.getTaskId();
        List<String> candidateIds = records.stream().map(CandidatePageVO::getId).collect(Collectors.toList());
        List<CandidateSubjectDTO> allCandidateSubjects = iCandidateSubjectService.getByCandidateIdsAndTaskId(candidateIds, taskId);
        Map<String, List<CandidateSubjectDTO>> candidateSubjectsMap = allCandidateSubjects.stream().collect(Collectors.groupingBy(CandidateSubjectDTO::getCandidateSubjectId));
        //收集考生的deptId
        List<String> deptIds = records.stream().map(CandidatePageVO::getDeptId).filter(StringUtils::isNotBlank).collect(Collectors.toList());
        //机构信息map key = deptId value = name
        Map<String,String> deptMap = getDeptInfo(deptIds);

        // 查询考生区域信息
        Map<String, TaskCandidateArea> mapTaskCandidateArea = iTaskCandidateAreaService.getTaskCandidateArea(taskId);

        // 查询考生证件类型
        Map<String,Dict> mapDict = iDictService.getCardTypeDict();

        // 处理分页数据
        records.stream().forEach(candidatePageVO -> {
            List<CandidateSubjectDTO> candidateSubjects = candidateSubjectsMap.getOrDefault(candidatePageVO.getId(), Collections.emptyList());
            List<CandidateSubjectParam> candidateSubjectParamList = new ArrayList<>();
            for (CandidateSubjectDTO candidateSubject : candidateSubjects) {
                CandidateSubjectParam candidateSubjectParam = new CandidateSubjectParam();
                String subjectId = candidateSubject.getSubjectId();
                Integer answerType = candidateSubject.getAnswerType();
                String subjectName = candidateSubject.getSubjectName();
                candidateSubjectParam
                        .setSubjectId(subjectId)
                        .setAnswerType(answerType);
                candidateSubjectParamList.add(candidateSubjectParam);

                if (StringUtils.isBlank(candidatePageVO.getSubjectName())) {
                    candidatePageVO.setSubjectName(subjectName);
                } else {
                    candidatePageVO.setSubjectName(StringUtils.join(candidatePageVO.getSubjectName(),",",subjectName));
                }
            }
            candidatePageVO.setSubjects(candidateSubjectParamList);

            //组装考生区域信息
            if (StringUtils.isNotBlank(candidatePageVO.getDistrict()) ) {
                Optional.ofNullable(mapTaskCandidateArea.get(candidatePageVO.getProvince()))
                        .ifPresent(province -> candidatePageVO.setProvinceName(province.getName()));
                Optional.ofNullable(mapTaskCandidateArea.get(candidatePageVO.getCity()))
                        .ifPresent(city -> candidatePageVO.setCityName(city.getName()));
                Optional.ofNullable(mapTaskCandidateArea.get(candidatePageVO.getDistrict()))
                        .ifPresent(district -> candidatePageVO.setDistrictName(district.getName()));
            }

            //组装证件类型
            Dict dict = mapDict.get(candidatePageVO.getIdType());
            candidatePageVO.setIdTypeName(null != dict ? dict.getLabel() : "");

            //组装照片全路径
            if (StringUtils.isNotBlank(candidatePageVO.getPersonalPhotoPath())) {
                candidatePageVO.setPhotoPath(candidatePageVO.getPersonalPhotoPath());
            }
            //设置考生的所属机构名称
            candidatePageVO.setOrgName(deptMap.get(candidatePageVO.getDeptId()));

            //设置区域
            candidatePageVO.setAreaName(joinAreaName(candidatePageVO.getProvince(),candidatePageVO.getCity(),
                    candidatePageVO.getDistrict(),mapTaskCandidateArea));
        });
        return page;
    }

    //获取机构信息map key = deptId value = name
    private Map<String,String> getDeptInfo(List<String> deptIds){
        Map<String,String> map = new HashMap<>();
        if(CollectionUtils.isEmpty(deptIds)){
            return map;
        }
        Map<String, String> listRes = iUaDeptService.getAncestorsNames(deptIds);
        if(CollectionUtils.isEmpty(listRes)){
            return map;
        }
        return listRes;
    }

    //拼接区域名称
    private String joinAreaName(String province,String city,String district,Map<String, TaskCandidateArea> mapTaskCandidateArea){
        // 查询考生区域信息
        StringBuilder sb = new StringBuilder();
        if(StringUtils.isNotBlank(province) && Objects.nonNull(mapTaskCandidateArea.get(province))){
            sb.append(mapTaskCandidateArea.get(province).getName()).append("/");
        }
        if(StringUtils.isNotBlank(city) && Objects.nonNull(mapTaskCandidateArea.get(city))){
            sb.append(mapTaskCandidateArea.get(city).getName()).append("/");
        }
        if(StringUtils.isNotBlank(district) && Objects.nonNull(mapTaskCandidateArea.get(district))){
            sb.append(mapTaskCandidateArea.get(district).getName());
        }
        String area = sb.toString();
        if(StringUtils.isNotBlank(area) && area.length() == (area.lastIndexOf("/") + 1)){
            area = area.substring(0, area.lastIndexOf("/"));
        }
        return area;
    }

    @Override
    public CandidateBaseInfoVO query(String candidateId)  {
        //查询考生基本信息
        CandidateParam candidateParam = candidateMapper.getCandidateInfo(candidateId);
        if (ObjectUtils.isEmpty(candidateParam)) {
            //考生不存在
            return null;
        }
        CandidateBaseInfoVO baseInfoVo = new CandidateBaseInfoVO();
        baseInfoVo.setName(candidateParam.getName());
        baseInfoVo.setIdNumber(candidateParam.getIdNumber());
        baseInfoVo.setBirthday(candidateParam.getBirthday());
        baseInfoVo.setSexName(candidateParam.getSex() == null?"":candidateParam.getSex() == 1?"男":"女");
        baseInfoVo.setPhone(candidateParam.getPhone());
        baseInfoVo.setEmail(candidateParam.getEmail());
        BeanUtils.copyProperties(candidateParam,baseInfoVo);
        //查询考生证件类型
        Map<String,Dict> mapDict = iDictService.getCardTypeDict();
        baseInfoVo.setIdTypeName(mapDict.get(String.valueOf(candidateParam.getIdType())).getLabel());
        //处理机构
        Map<String, String> deptInfo = getDeptInfo(Lists.newArrayList(candidateParam.getDeptId()));
        baseInfoVo.setOrgName(deptInfo.get(baseInfoVo.getDeptId()));
        //处理区域

        //查询考生照片信息
        QueryWrapper<CandidatePhoto> candidateQueryWrapper = new QueryWrapper<>();
        candidateQueryWrapper.lambda()
                .eq(CandidatePhoto::getCandidateId, candidateId)
                .eq(CandidatePhoto::getDelFlag, false)
                .orderByAsc(CandidatePhoto::getPhotoType);
        List<CandidatePhoto> candidatePhotoList = iCandidatePhotoService.list(candidateQueryWrapper);
        //当考生照片不为空的情况下 组装考生照片
        if(candidatePhotoList != null && !candidatePhotoList.isEmpty()){
            String photoPath = candidatePhotoList.get(0).getPhotoPath();
            baseInfoVo.setPhotoPathAll(photoPath);
        }
        //组装区域信息
        Map<String, TaskCandidateArea> mapTaskCandidateArea = iTaskCandidateAreaService.getTaskCandidateArea(candidateParam.getTaskId());
        if(!mapTaskCandidateArea.isEmpty()){
            StringBuffer  address = new StringBuffer();
            String province = StringUtils.isEmpty(candidateParam.getProvince()) ? " " : mapTaskCandidateArea.get(candidateParam.getProvince()).getName(),
                    city = StringUtils.isEmpty(candidateParam.getCity()) ? " " : mapTaskCandidateArea.get(candidateParam.getCity()).getName(),
                    area = StringUtils.isEmpty(candidateParam.getDistrict()) ? " " : mapTaskCandidateArea.get(candidateParam.getDistrict()).getName();
            if(StringUtils.isNotEmpty(province)){
                address.append(province).append(" ");
            }
            if(StringUtils.isNotEmpty(city)){
                address.append(city).append(" ");
            }
            if(StringUtils.isNotEmpty(area)){
                address.append(area);
            }
            String failAddress = address.toString();
            if(StringUtils.isNotEmpty(failAddress)){
                baseInfoVo.setAddress(failAddress);
            }
        }
        baseInfoVo.setAreaName(joinAreaName(candidateParam.getProvince(),candidateParam.getCity(),candidateParam.getDistrict()
                ,mapTaskCandidateArea));
        //查询考生科目信息并组装科目信息
        List<String> subjectNames =  iCandidateSubjectService.getCandidateSubjectNames(candidateId);
        if(!subjectNames.isEmpty() && subjectNames.size() > 0){
            String joinSubjectNames = Joiner.on(",").join(subjectNames);
            baseInfoVo.setSubject(joinSubjectNames);
        }
        //获取考生准考证号
        List<CustomArrange> customArrangeList = iCustomArrangeService.listByCandidateId(candidateId);
        String admissionNumbers = customArrangeList.stream()
                .map(CustomArrange::getAdmissionNumber)
                .filter(StringUtils::isNotBlank)
                .distinct()
                .collect(Collectors.joining(","));
        baseInfoVo.setAdmissionNumber(admissionNumbers);
        return baseInfoVo;
    }

    @Override
    @Transactional(noRollbackFor = DataBeingLockedException.class)
    public void update(CandidateParam candidateParam) throws Exception {
        //判断考试任务类型（线上机考、线下机考）
        Task task = iTaskService.getTaskById(candidateParam.getTaskId());
        String taskExamType = task.getExamType();
        switch (TaskExamTypeEnum.getByCode(taskExamType)) {
            //线上机考
            case ONLINE:
                this.updateByOnlineExam(candidateParam, task);
                break;
            //线下机考
            case OFFLINE:
                this.updateByOfflineExam(candidateParam);
                break;
        }
    }

    /**
     * 更新线上机考考生信息
     * @param candidateParam 更新考生信息参数
     */
    private void updateByOnlineExam(CandidateParam candidateParam, Task task) throws Exception {
        //线上机考绑定报名的情况下不允许修改
        //: 如果任务绑定报名，则不允许修改考生
        iTaskService.checkBindBm(candidateParam.getTaskId());
        //获取数据锁定状态，数据已锁定则表示考生数据已经推送至考试端，反之则未推送
        boolean lock = Optional.ofNullable(task.getDataLock())
                .orElse(false);
        if (lock) {
            //数据已锁定，则只更新手机号、邮箱，并推送至考试端
            //校验手机号
            if (StringUtils.isNotBlank(candidateParam.getPhone())) {
                if (!StringUtil.isMobile(candidateParam.getPhone())) {
                    //手机号格式不合法
                    throw new KqException(ResponseCode.ADD_CANDIDATE_INVALID_PHONE.getCode(),
                            ResponseCode.ADD_CANDIDATE_INVALID_PHONE.getMsg());
                }
            }
            //校验邮箱
            if (StringUtils.isNotBlank(candidateParam.getEmail())) {
                if (!Validator.isEmail(candidateParam.getEmail())) {
                    //邮箱格式不合法
                    throw new KqException(ResponseCode.ADD_CANDIDATE_INVALID_EMAIL.getCode(),
                            ResponseCode.ADD_CANDIDATE_INVALID_EMAIL.getMsg());
                }
            }
            //更新考务端考生的手机号、邮箱字段
            Candidate candidate = new Candidate();
            candidate.setPhone(candidateParam.getPhone())
                    .setEmail(candidateParam.getEmail())
                    .setId(candidateParam.getId());
            this.updateById(candidate);
            //将修改考生信息更新至考试端
            this.updateCandidateInfoToExam(candidateParam);
            //数据锁定的情况下仅更新手机号、邮箱，不再做其他操作
            throw new DataBeingLockedException(ResponseCode.UPDATE_CANDIDATE_DATA_LOCK.getCode(),
                    ResponseCode.UPDATE_CANDIDATE_DATA_LOCK.getMsg());
        }
        //数据未锁定，全量更新所有考生信息，修改结果不推送至考试端
        //更新考生
        this.updateCandidate(candidateParam);
        /*//处理考生的区域
        //先查省
        String taskId = task.getId();
        List<TaskCandidateArea> areas = new ArrayList<>(3);
        if (Objects.nonNull(candidateParam.getProvince())) {
            TaskCandidateArea p = iTaskCandidateAreaService.listByTaskIdAndAreaId(taskId, candidateParam.getProvince());
            if (Objects.nonNull(p)) {
                TaskCandidateArea officeArea = new TaskCandidateArea();
                officeArea.setAreaId(p.getAreaId())
                        .setTaskId(taskId)
                        .setParentId("0")
                        .setParentIds("0")
                        .setName(p.getName())
                        .setFullName(p.getName())
                        .setType("2")
                        .setId(UUIDUtils.randomUUID());
                areas.add(officeArea);
                if (Objects.nonNull(candidateParam.getCity())) {
                    TaskCandidateArea c = iTaskCandidateAreaService.listByTaskIdAndAreaId(taskId, candidateParam.getCity());
                    if (Objects.nonNull(c)) {
                        TaskCandidateArea cArea = new TaskCandidateArea();
                        cArea.setAreaId(c.getAreaId())
                                .setTaskId(taskId)
                                .setParentId(p.getAreaId())
                                .setParentIds("0" + p.getAreaId())
                                .setName(c.getName())
                                .setFullName(p.getName() + "/" + c.getName())
                                .setType("3")
                                .setId(UUIDUtils.randomUUID());
                        areas.add(cArea);
                    }
                    if (Objects.nonNull(candidateParam.getDistrict())) {
                        TaskCandidateArea d = iTaskCandidateAreaService.listByTaskIdAndAreaId(taskId, candidateParam.getDistrict());
                        if (Objects.nonNull(d)) {
                            TaskCandidateArea dArea = new TaskCandidateArea();
                            dArea.setAreaId(d.getAreaId())
                                    .setTaskId(taskId)
                                    .setParentId(c.getAreaId())
                                    .setParentIds("0" + p.getAreaId() + c.getAreaId())
                                    .setName(d.getName())
                                    .setFullName(p.getName() + "/" + c.getName() + "/" + d.getName())
                                    .setType("4")
                                    .setId(UUIDUtils.randomUUID());
                            areas.add(dArea);
                        }
                    }
                }
            }
        }
        if (!iTaskCandidateAreaService.saveAreaWhenIsExists(taskId,areas)) {
            //插入考生表失败
            throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getCode(), ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getMsg());
        }*/
    }

    /**
     * 将修改考生信息更新至考试端
     * @param candidateParam 更新考生信息参数
     */
    private void updateCandidateInfoToExam(CandidateParam candidateParam) {
        //组装更新考生信息数据
        PushTimeRoomCandidateVO pushTimeRoomCandidateVO = new PushTimeRoomCandidateVO();
        pushTimeRoomCandidateVO.setCandidateId(candidateParam.getId())
                .setTaskId(candidateParam.getTaskId())
                .setEmail(candidateParam.getEmail())
                .setPhone(candidateParam.getPhone());
        //更新考试端考生信息数据
        ApiResponse<?> response = ksClient.updateCandidate(pushTimeRoomCandidateVO);
        Integer code = Optional.ofNullable(response)
                .map(ApiResponse::getCode)
                .orElseThrow(() -> new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_EXCEPTION.getCode(),
                        "调用考试端更新考生信息失败"));
        if (!ResponseCode.SUCCESS.getCode().equals(code)) {
            log.error("# 将修改考生信息更新至考试端失败，错误码为: {}", code);
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_EXCEPTION.getCode(),
                    "将修改考生信息更新至考试端失败");
        }
    }

    /**
     * 更新线下机考考生信息
     * @param candidateParam 更新考生信息参数
     */
    private void updateByOfflineExam(CandidateParam candidateParam) {
        //线下机考绑定报名的情况下不允许修改
        //: 如果任务绑定报名，则不允许修改考生
        iTaskService.checkBindBm(candidateParam.getTaskId());
        //线下机考在数据锁定的情况下不允许修改
        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowException(candidateParam.getTaskId());
        //更新考生
        this.updateCandidate(candidateParam);
    }

    /**
     * 更新考生
     * @param candidateParam 更新考生参数
     */
    private void updateCandidate(CandidateParam candidateParam) {
        //校验考生信息
        this.verifyCandidateInfo(candidateParam);
        //更新考生照片
        this.updateCandidatePhoto(candidateParam);
        //更新考生信息
        this.updateCandidateInfo(candidateParam);
        //更新考生科目
        this.updateCandidateSubject(candidateParam);
    }

    @Override
    public List<CandidateInfoObtResultDTO> getCandidateInfoObtResultDTO(Integer personalPhoto, String taskCode, String admissionNumber) {
        return candidateMapper.getCandidateInfoObtResultDTO(personalPhoto, taskCode, admissionNumber);
    }

    @Override
    public List<CandidateInfoGceResultDTO> getCandidateInfoGceResultDTO(Integer personalPhoto, String taskCode, String admissionNumber) {
        return candidateMapper.getCandidateInfoGceResultDTO(personalPhoto, taskCode, admissionNumber);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void delete(String taskId, String candidateIds) {
        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowException(taskId);
        //: 如果任务绑定报名，则不允许删除考生
        iTaskService.checkBindBm(taskId);
        //转换考生id
        List<String> toDeleteIdList = Optional.ofNullable(candidateIds)
                .map(c ->
                        Arrays.stream(c.split(","))
                                .map(String::trim)
                                .collect(Collectors.toList())
                )
                .orElse(new ArrayList<>());
        //校验是否存在已编排考生
        if (iTimeRoomCandidateService.hasCandidateArranged(taskId, toDeleteIdList)) {
            //删除失败，当前删除数据中包含已编排的考生
            throw new KqException(ResponseCode.HAS_CANDIDATE_ARRANGED.getCode(),
                    ResponseCode.HAS_CANDIDATE_ARRANGED.getMsg());
        }
        //删除考生表数据
        this.deleteCandidates(taskId, toDeleteIdList);
        //删除考生照片数据
        iCandidatePhotoService.deleteCandidatePhotos(taskId, toDeleteIdList);
        //删除考生科目表数据
        iCandidateSubjectService.deleteCandidateSubjects(taskId, toDeleteIdList);
        //删除考生准考证号表数据
        iCustomArrangeService.deleteCandidateAdmissionNumbers(taskId, toDeleteIdList);
    }

    /**
     * 过滤掉无法删除的考生
     * @param candidateIds 待删除的考生id
     * @param arrangeCandidateInfoList 已编排的考生信息
     * @return 已过滤掉无法删除的考生的id
     */
    private String filtrationCandidate(String candidateIds, List<Candidate> arrangeCandidateInfoList) {
        //转换考生id数据
        List<String> toDeleteIds = Arrays.stream(candidateIds.split(","))
                .map(String::trim)
                .collect(Collectors.toList());
        //过滤掉无法删除的考生
        return toDeleteIds.stream()
                .filter(id -> arrangeCandidateInfoList.stream().noneMatch(info -> id.equals(info.getId())))
                .collect(Collectors.joining(","));


    }

    /**
     * 删除考生表数据
     * @param taskId 考试任务id
     * @param candidateIdList 考生id的List
     */
    private void deleteCandidates(String taskId, List<String> candidateIdList) {
        QueryWrapper<Candidate> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(Candidate::getTaskId, taskId)
                .in(!CollectionUtils.isEmpty(candidateIdList), Candidate::getId, candidateIdList)
                .eq(Candidate::getDelFlag, false);
        this.remove(queryWrapper);
    }

    /**
     * 更新考生科目
     * @param candidateParam 更新考生参数
     * @throws KqException 更新考生科目失败，抛出异常
     */
    private void updateCandidateSubject(CandidateParam candidateParam) throws KqException {
        List<CandidateSubjectParam> subjects = candidateParam.getSubjects();
        //获取当前考生科目数据
        QueryWrapper<CandidateSubject> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(CandidateSubject::getCandidateId, candidateParam.getId())
                .eq(CandidateSubject::getTaskId, candidateParam.getTaskId())
                .eq(CandidateSubject::getDelFlag, false);
        List<CandidateSubject> candidateSubjectList = iCandidateSubjectService.list(queryWrapper);


        //去掉用户取消报考的科目
        candidateSubjectList = candidateSubjectList.stream()
                .filter(candidateSubject -> subjects.stream()
                        .anyMatch(subject -> subject.getSubjectId().equals(candidateSubject.getSubjectId())))
                .collect(Collectors.toList());

        //对考生的科目进行去重
        candidateSubjectList = candidateSubjectList.stream().
                collect(Collectors.collectingAndThen(Collectors.toCollection(() ->
                        new TreeSet<>(Comparator.comparing(CandidateSubject::getSubjectId))), ArrayList::new));

        //更新考生科目关联数据
        candidateSubjectList.forEach(candidateSubject -> subjects.forEach(subject -> {
            //设置作答方式
            if (candidateSubject.getSubjectId().equals(subject.getSubjectId())) {
                candidateSubject.setAnswerType(subject.getAnswerType());
            }
        }));

        //添加用户新报考的科目
        List<CandidateSubject> finalCandidateSubjectList = candidateSubjectList;
        subjects.forEach(subject -> {
            if (finalCandidateSubjectList.stream().noneMatch(candidateSubject -> subject.getSubjectId().equals(candidateSubject.getSubjectId()))) {
                CandidateSubject candidateSubject = new CandidateSubject();
                candidateSubject.setTaskId(candidateParam.getTaskId())
                        .setCandidateId(candidateParam.getId())
                        .setSubjectId(subject.getSubjectId())
                        .setAnswerType(subject.getAnswerType())
                        .setId(UUIDUtils.newSortUUID());
                finalCandidateSubjectList.add(candidateSubject);
            }
        });

        //删除旧的考生科目数据
        if (!iCandidateSubjectService.remove(queryWrapper)) {
            throw new KqException(ResponseCode.ERROR_UPDATE_DELETE_CANDIDATE_SUBJECT_FAIL.getCode(),ResponseCode.ERROR_UPDATE_DELETE_CANDIDATE_SUBJECT_FAIL.getMsg());
        }
        //插入新的考生科目数据
        if (!iCandidateSubjectService.saveBatch(finalCandidateSubjectList)) {
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_INSERT_SUBJECT_FAIL.getCode(), ResponseCode.ERROR_UPDATE_CANDIDATE_INSERT_SUBJECT_FAIL.getMsg());
        }
    }

    /**
     * 更新考生信息
     * @param candidateParam 更新考生参数
     * @throws KqException 更新考生信息失败，抛出异常
     */
    private void updateCandidateInfo(CandidateParam candidateParam) throws KqException {
        //获取考生表数据
        QueryWrapper<Candidate> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(Candidate::getId, candidateParam.getId())
                .eq(Candidate::getDelFlag, false);
        Candidate candidate = iCandidateService.getOne(queryWrapper);
        if (ObjectUtils.isEmpty(candidate)) {
            //考生已不存在
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_NON_EXISTENT.getCode(),ResponseCode.ERROR_UPDATE_CANDIDATE_NON_EXISTENT.getMsg());
        }
        //1.如果客户端有更新见证号码 则判断是否重复
        if(!candidateParam.getIdNumber().equalsIgnoreCase(candidate.getIdNumber())){
            List<Candidate> findByIdNumber = iCandidateService.lambdaQuery().
                    eq(Candidate::getIdNumber, candidateParam.getIdNumber())
                    .eq(Candidate::getTaskId, candidateParam.getTaskId())
                    .eq(Candidate::getDelFlag, false)
                    .list();
            //2.证件号重复
            if(TextUtil.isNotNull(findByIdNumber)){
                throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_ID_NUMBER_EXISTENT.getCode(),ResponseCode.ERROR_UPDATE_CANDIDATE_ID_NUMBER_EXISTENT.getMsg());
            }
        }
        //处理考生数据
        BeanUtils.copyProperties(candidateParam, candidate);
        //更新考生表
        if (!iCandidateService.updateById(candidate)) {
            //更新考生表失败
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_FAIL.getCode(), ResponseCode.ERROR_UPDATE_CANDIDATE_FAIL.getMsg());
        }
        if(Objects.isNull(candidateParam.getDeptId())){
            LambdaUpdateWrapper<Candidate> updateC = Wrappers.lambdaUpdate();
            updateC.eq(Candidate::getId,candidate.getId());
            updateC.set(Candidate::getDeptId,null);
            if(!this.update(updateC)){
                //更新考生表失败
                throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_FAIL.getCode(), ResponseCode.ERROR_UPDATE_CANDIDATE_FAIL.getMsg());
            }
        }
    }

    /**
     * 更新考生照片
     * @param candidateParam 更新考生参数
     * @throws  ，抛出异常
     */
    private void updateCandidatePhoto(CandidateParam candidateParam) throws KqException {
        //: 目前只支持一种照片类型，后续若需要支持多种照片类型需要修改此处代码
        //处理考生照片数据
        QueryWrapper<CandidatePhoto> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(CandidatePhoto::getCandidateId, candidateParam.getId())
                .eq(CandidatePhoto::getDelFlag, false);
        //获取照片
        String photoPath = candidateParam.getPhotoPath();
        CandidatePhoto candidatePhoto = iCandidatePhotoService.getOne(queryWrapper);
        //1.入参照片不为空 并且照片与之前的照片不一致的情况下
       if(TextUtil.isNotNull(photoPath)){
           //2.如果照片记录为空
          if(TextUtil.isNull(candidatePhoto)){
                  //当前考生还未上传照片
                  CandidatePhoto addCandidatePhoto = new CandidatePhoto();
                  addCandidatePhoto.setCandidateId(candidateParam.getId())
                          .setTaskId(candidateParam.getTaskId())
                          .setPhotoType(PhotoTypeEnum.PERSONAL.getCode())
                          .setPhotoPath(candidateParam.getPhotoPath())
                          .setName(candidateParam.getPhotoName());
                  //插入考生照片表
                  if (!iCandidatePhotoService.save(addCandidatePhoto)) {
                      throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getCode(), ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getMsg());
                  }
                  return;
          }else{
              //3.如果记录存在则更新照片
              if(!photoPath.equals(candidatePhoto.getPhotoPath())){
                  //3.1赋值新的照片
                  candidatePhoto.setPhotoPath(photoPath);
                  candidatePhoto.setTaskId(candidateParam.getTaskId());
                  String photoName = candidateParam.getPhotoName();
                  if(TextUtil.isNull(photoName)){
                      throw new KqException(ResponseCode.UPLOAD_PHOTO_PARAM_NAME_INVALID.getCode(),ResponseCode.UPLOAD_PHOTO_PARAM_NAME_INVALID.getMsg());
                  }
                  //3.2赋值新的照片文件名
                  candidatePhoto.setName(photoName);
                  if (!iCandidatePhotoService.updateById(candidatePhoto)) {
                      //更新考生照片表失败
                      throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_PHOTO_FAIL.getCode(),ResponseCode.ERROR_UPDATE_CANDIDATE_PHOTO_FAIL.getMsg());
                  }
              }

          }
       }
    }

    @Override
    public Boolean candidateExits(String taskId) {
        Integer count = this.lambdaQuery()
                .eq(Candidate::getTaskId,taskId)
                .eq(Candidate::getDelFlag,false)
                .count();
        if (count > 0) {
            return true;
        }
        return false;
    }

    @Override
    public List<TaskCandidateAreaExcelData> getTaskCandidateAreaSheet(String taskId) {
        //0.根据任务ID获取区域县区级
        List<TaskCandidateArea> districtList = iTaskCandidateAreaService.getTaskCandidateAreaDistrictList(taskId);
        if(!districtList.isEmpty() && districtList.size() > 0){
            //1.获取区域字典
            Map<String, TaskCandidateArea> areaDict = iTaskCandidateAreaService.getTaskCandidateArea(taskId);
            //2.声明列表变量
            List<TaskCandidateAreaExcelData>  sheets = null;
            //3.如果字典列表不为空
            if(!areaDict.isEmpty() && areaDict.size() > 0){
                //4.先初始化一个列表
                sheets =   new ArrayList<TaskCandidateAreaExcelData>();
                //5.遍历并赋值组装
                for(TaskCandidateArea district:districtList){
                    //5.0初始化一个完整区域对象
                    TaskCandidateAreaExcelData sheet = new TaskCandidateAreaExcelData();
                    sheet.setDistrictName(district.getName());
                    sheet.setDistrictId(district.getAreaId());
                    //5.1根据县区父级ID获取市级对象
                    TaskCandidateArea city = areaDict.get(district.getParentId());
                    if(!ObjectUtils.isEmpty(city)){
                        sheet.setCityId(city.getAreaId());
                        sheet.setCityName(city.getName());
                        //5.2根据市区父级ID获取省级对象并组装
                        TaskCandidateArea province = areaDict.get(city.getParentId());
                        if(!ObjectUtils.isEmpty(province)){
                            sheet.setProvinceId(province.getAreaId());
                            sheet.setProvinceName(province.getName());
                        }
                    }
                    //5.3将组装好的省市县区信息添加到列表
                    sheets.add(sheet);
                };
            }
            return  sheets;
        }
        return null;
    }

    @Override
    public void exportCandidateTemplate(HttpServletResponse response, String taskId, User user) {
        ExcelWriter writer = null;
        if(TextUtil.isNull(taskId)){
            throw new KqException(ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_TASK_ID_NULL.getCode(),"任务ID不能为空！");
        }else{

            try{
                String fileName = URLEncoder.encode("导入考生信息模板", "UTF-8");
                response.setContentType("application/vnd.ms-excel");
                response.setCharacterEncoding("utf-8");
                response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
                ClassPathResource resource = new ClassPathResource("templates/candidate/candidate.xlsx");
                ServletOutputStream out = response.getOutputStream();
                writer = EasyExcel.write(out).withTemplate(resource.getInputStream()).build();
                WriteSheet sheet1 = EasyExcel.writerSheet(0).build();
                WriteSheet sheet2 = EasyExcel.writerSheet(1).build();
                // 区域
                //List<OfficeAreaSheet> officeAreaSheet = iSubjectService.getOfficeAreaSheet();
                List<OfficeAreaSheet> officeAreaSheet = this.getOfficeAreaSheet(taskId);
                writer.write(officeAreaSheet, sheet1);
                // 所属组织
                writer.write(iTaskRoomService.getDeptData(user), sheet2);

            }catch (Exception e){
                log.error("# 导入考生信息模板生成异常:", e);
                //e.printStackTrace();
                throw new KqException(ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_GENERATE_FAIL.getCode(),ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_GENERATE_FAIL.getMsg());
            } finally {
                if(Objects.nonNull(writer)){
                    writer.finish();
                }
            }
        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void batchImportCandidate(String file, String taskId, User user) {
        //考试任务id不能为空
        if (StringUtils.isBlank(taskId)) {
            BusinessLogicException.throwException(43051402, "任务id不能为空");
        }
        String fileSuffix = FileUtil.getSuffix(file);
        if (!StrUtil.equalsAny(fileSuffix, "xlsx", "xls")) {
            BusinessLogicException.throwException(43051404, "上传的文件类型不匹配", fileSuffix);
        }
        File uploadFile = FileUtil.createTempFile(StrPool.DOT + fileSuffix, Boolean.TRUE);
        long fileSize = HttpUtil.downloadFile(file, uploadFile);
        if (fileSize == CommonConstant.ZERO) {
            BusinessLogicException.throwException(43051403, "请勿上传一个空文件");
        }
        ProgressManager.updateProgressCurrent(3L, "文件接收成功");
        // 异常信息
        List<ExcelErr> errList = new ArrayList<>();
        //不重复的值 考生导入
        List<ImportTaskCandidateSheet> noRepeatCandidateSheets = new ArrayList<>();
        List<ImportTaskCandidateSheet> importCandidates = new ArrayList<>();
        List<ImportSelfAreaSheet> areaList =  new ArrayList<>();

        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowForImport(taskId);
        //如果任务绑定报名，则不允许添加考生.
        iTaskService.checkBindBmForImport(taskId);
        ProgressManager.updateProgressCurrent(8L, "考试任务状态检测成功");
        //校验文件
        this.validFile(uploadFile);
        ProgressManager.updateProgressCurrent(12L, "文件大小及类型验证完成");
        //分析数据
        readExcel(uploadFile, importCandidates,noRepeatCandidateSheets,areaList,taskId);
        //判断是否为空的模板文件
        if(CollectionUtils.isEmpty(noRepeatCandidateSheets) && CollectionUtils.isEmpty(importCandidates)){
            BusinessLogicException.throwException(43051403, "请勿上传一个空文件");
        }
        ProgressManager.updateProgressCurrent(38L, "Excel数据读取完成");
        //用与存放对象
        List<Candidate> candidates = new ArrayList<>();
        Map<String, ImportSelfAreaSheet> districtMap = new HashMap<>();
        //2:查询科目用与判断
        LambdaQueryWrapper<Subject> subjectQuery = Wrappers.lambdaQuery();
        subjectQuery.eq(Subject::getTaskId,taskId).eq(Subject::getDelFlag,Boolean.FALSE);
        List<Subject> subjects = iSubjectService.getBaseMapper().selectList(subjectQuery);
        //key = name value = object
        Map<String, Subject> subjectMap = new HashMap<>();
        if(CollectionUtil.isNotEmpty(subjects)){
             subjectMap = subjects.stream().collect(Collectors.toMap(Subject::getName,Function.identity(),(k1,k2) -> k1));
        }
        //1.获取任务下已存在的考生科目数据
        List<ImportTaskCandidateSheet> existedCandidateSubjectList = baseMapper.getExistedCandidateSubject(taskId);
        //机构层级
        Map<String, Organization> orgMap = iSubjectService.getOfficeAllDepts();
        ProgressManager.updateProgressCurrent(42L, "Excel数据验证开始准备工作完成");

        // 一个sheet异常记录
        ExcelErr sheet3Err = ExcelErr.builder().sheetNo(0).sheetName("区域信息").build();
        List<TaskCandidateArea> selfLevelList = new ArrayList<>();
        List<ExcelRowErr> row3Errs = analysisSelfLevelExcelData(taskId, areaList, selfLevelList, districtMap);
        sheet3Err.setRowErrList(row3Errs);
        errList.add(sheet3Err);
        ProgressManager.updateProgressCurrent(61L, "Excel数据验区域信息完成");

        // 一个sheet异常记录
        ExcelErr sheet2Err = ExcelErr.builder().sheetNo(2).sheetName("考生信息").build();

        List<ImportTaskCandidateSheet> result = new ArrayList<ImportTaskCandidateSheet>();
        Map<String,ImportTaskCandidateSheet> candidateMap = new HashMap<>();
        Set<String> subjectSet = new HashSet<String>();
        for(int i = 0 ; i < importCandidates.size(); i ++){
            ImportTaskCandidateSheet candidateSheet = importCandidates.get(i);
            if(StrUtil.isNotBlank(candidateSheet.getIdNumber())){
                if(candidateMap.containsKey(candidateSheet.getIdNumber())){//合并相同证件号的科目
                    ImportTaskCandidateSheet candidate = candidateMap.get(candidateSheet.getIdNumber());
                    candidate.setSubject(candidate.getSubject() + "#" +candidateSheet.getSubject());
                    candidateMap.put(candidateSheet.getIdNumber(),candidate);
                } else {
                    candidateMap.put(candidateSheet.getIdNumber(),candidateSheet);
                }
            }
            if(StrUtil.isNotBlank(candidateSheet.getSubject())){
                List<String> names = Arrays.asList(candidateSheet.getSubject().split("#"));
                for(String name : names){
                    if(!subjectMap.containsKey(name)){
                        subjectSet.add(name);
                    }
                }
            }
        }
        List<Subject> notExistSubjectList = this.saveSubject(subjectSet,taskId);

        // 解析考点考场sheet
        List<ExcelRowErr> rowErrs = this.analysisExcelData(taskId, importCandidates, noRepeatCandidateSheets,candidates, districtMap,
                areaList,subjectMap,existedCandidateSubjectList,orgMap,candidateMap);
        sheet2Err.setRowErrList(rowErrs);
        errList.add(sheet2Err);
        if(!CollectionUtils.isEmpty(row3Errs) || !CollectionUtils.isEmpty(rowErrs)){
            ProgressManager.finish(ProgressResult.builder().success(Boolean.FALSE).result(errList).build());
            return;
        }
        ProgressManager.updateProgressCurrent(79L, "Excel数据验考生采集信息完成");

        //保存数据
        //开始保存数据
        //1.验证科目并返回组装好的考生科目数据
        List<CandidateSubject> candidateSubjects = this.saveCandidateSubject(notExistSubjectList,importCandidates,
                taskId, existedCandidateSubjectList,subjectMap);
        //2 组装自定义编排数据
        List<CustomArrange> customArrangeList = this.convertCustomAdmissionNumber(taskId, importCandidates);
        //3.组装考生入库数据 --
        noRepeatCandidateSheets.forEach(sheet -> {
            Candidate candidate = new Candidate();
            BeanUtils.copyProperties(sheet, candidate);
            candidate.setSex(NumberUtils.isDigits(sheet.getSex()) ? Integer.valueOf(sheet.getSex()) : null);
            candidate.setIdType(NumberUtils.isDigits(sheet.getIdType()) ? Integer.valueOf(sheet.getIdType()) : null);
            candidate.setImportType(0);

            changeAreaSelf(districtMap.get(sheet.getMinAreaLevel()), candidate);

            candidates.add(candidate);
            //处理officeId
            if(Objects.nonNull(orgMap.get(sheet.getDeptId()))){
                Organization org = orgMap.get(sheet.getDeptId());
                candidate.setOfficeId(org.getOfficeid());
                candidate.setDeptId(org.getId());
            }
        });
        ProgressManager.updateProgressCurrent(83L, "Excel导入数据组装完成");
        if(CollectionUtil.isNotEmpty(notExistSubjectList)){
           iSubjectService.saveBatch(notExistSubjectList);
        }
        //4.拆分数据进行导入
        this.saveCandidates(candidates);
        ProgressManager.updateProgressCurrent(86L, "Excel考生入库完成");
        //5.拆分批量保存考生科目
        this.saveCandidatesSubject(candidateSubjects);
        ProgressManager.updateProgressCurrent(89L, "Excel考生科目入库完成");
        //6.保存考生准考证号数据
        this.saveCandidateAdmissionNumber(customArrangeList);
        ProgressManager.updateProgressCurrent(92L, "Excel考生科目入库完成");
        //7.保存考生自定义区域入库
        if(!CollectionUtils.isEmpty(selfLevelList)){
            if(!iTaskCandidateAreaService.saveBatch(selfLevelList)){
                throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                        "导入失败");
            }
        }
        ProgressManager.updateProgressCurrent(95L, "Excel考生区域层级入库完成");
        //8.修改task中的报名任务id
        iTaskService.lambdaUpdate()
                .eq(Task::getId, taskId)
                .set(Task::getTaskBmId, "0")
                .update();
        ProgressManager.updateProgressCurrent(99L, "Excel导入考生完成");
        ProgressManager.finish(ProgressResult.builder().success(Boolean.TRUE).build());
    }

    private List<Subject> saveSubject(Set<String> subjectSet,String taskId) {
        List<Subject> subjectList = new ArrayList<>();
        int code = iSubjectService.getLastTimeRoomCode(taskId);
        if(!subjectSet.isEmpty()){
            for(String subjectStr : subjectSet){
                Subject subject = new Subject();
                subject.setTaskId(taskId);
                code = code + 1;
                subject.setCode(code+"");
                subject.setName(subjectStr);
                subject.setSubjectName(subjectStr);
                subject.setId(UUIDUtils.newSortUUID());
                subjectList.add(subject);
            }
        }
        return subjectList;
    }

    //分析导入的数据
    private List<ExcelRowErr> analysisExcelData(String taskId, List<ImportTaskCandidateSheet> importCandidates,
                                                List<ImportTaskCandidateSheet> noRepeatCandidateSheets,
                                                List<Candidate> candidates, Map<String, ImportSelfAreaSheet> districtMap,
                                                List<ImportSelfAreaSheet> areaList,Map<String, Subject> subjectMap,
                                                List<ImportTaskCandidateSheet> existedCandidateSubjectList,
                                                Map<String, Organization> orgMap,Map<String,ImportTaskCandidateSheet> candidateMap) {
        List<ExcelRowErr> rowErrs = new ArrayList<>();

        //官方区域
        Map<String, TaskArea> officeAreaMap = iSubjectService.getAllAreaMap();
        Map<Integer,String> isSelfMinLevel = new HashMap<>(8);
        Map<Integer,String> isAdmissionNum = new HashMap<>(8);
        //用与保存自定义准考证是否重复
        Set<String> selfAdmisson = new HashSet<>();
        //存在系统里考生的准考证
        LambdaQueryWrapper<CustomArrange> caQuery = Wrappers.lambdaQuery();
        caQuery.eq(CustomArrange::getTaskId,taskId).isNotNull(CustomArrange::getAdmissionNumber).eq(CustomArrange::getDelFlag,Boolean.FALSE);
        caQuery.select(CustomArrange::getAdmissionNumber);
        List<CustomArrange> list = iCustomArrangeService.list(caQuery);
        Set<String> admissons = list.stream().map(CustomArrange::getAdmissionNumber).collect(Collectors.toSet());
        //考生是否重复 key = 姓名 value = idcard
        Set<String> sheetCandidateMap = new HashSet<>(importCandidates.size());

        for(int i = 0 ; i < importCandidates.size(); i ++){
            ImportTaskCandidateSheet candidateSheet = importCandidates.get(i);
            //考生每列的数据
            List<ExcelLineErr> lineErrs = checkValue(taskId,candidateSheet,subjectMap,districtMap,officeAreaMap,
                    orgMap,areaList,isSelfMinLevel,isAdmissionNum,selfAdmisson,admissons,sheetCandidateMap,candidateMap);
            if(!CollectionUtils.isEmpty(lineErrs)){
                ExcelRowErr rowErr = ExcelRowErr.builder().row(i +1).lineErrList(lineErrs).build();
                rowErrs.add(rowErr);
                continue;
            }
            //是否与数据库的一样的考生
            Optional<ImportTaskCandidateSheet> first = existedCandidateSubjectList.stream()
                    .filter(ex -> ex.getIdNumber().equals(candidateSheet.getIdNumber())
                    && ex.getSubject().equals(candidateSheet.getSubject())).findFirst();
            if(first.isPresent()){
                List<ExcelLineErr> rowErrsId = new ArrayList<>();
                rowErrsId.add(ExcelLineErr.builder().line(3).msg(candidateSheet.getIdNumber() + " " + candidateSheet.getSubject() + " 存在重复数据").build());
                ExcelRowErr rowErr = ExcelRowErr.builder().row(i +1).lineErrList(rowErrsId).build();
                rowErrs.add(rowErr);
            }
        }
        if(!rowErrs.isEmpty()){
            return rowErrs;
        }
        return rowErrs;
    }



    private List<ExcelLineErr> checkValue(String taskId, ImportTaskCandidateSheet candidateSheet, Map<String, Subject> subjectMap,
                                          Map<String, ImportSelfAreaSheet> districtMap, Map<String, TaskArea> officeAreaMap,
                                          Map<String, Organization> orgMap, List<ImportSelfAreaSheet> areaList,
                                          Map<Integer,String> isSelfMinLevel, Map<Integer,String> isAdmissionNum,
                                          Set<String> selfAdmisson, Set<String> admissons, Set<String> sheetCandidateMap,Map<String,ImportTaskCandidateSheet> candidateMap){
        final String sexRegex = "[1-2]",
                idTypeRegex = "[0-2]",
                answerTypeRegex="[1-2]";
        List<ExcelLineErr> rowErrs = new ArrayList<>();
        ImportTaskCandidateSheet temp = null;
        if(StrUtil.isNotBlank(candidateSheet.getIdNumber()) && candidateMap.containsKey(candidateSheet.getIdNumber())){
            temp = candidateMap.get(candidateSheet.getIdNumber());
        }
        //姓名
        if(StringUtils.isBlank(candidateSheet.getName())){
            rowErrs.add(ExcelLineErr.builder().line(1).msg("考生名称不能为空").build());
        } else {
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getName().equals(temp.getName())){
                rowErrs.add(ExcelLineErr.builder().line(1).msg("考生名称:"+candidateSheet.getName()+"存在不一致").build());
            }
        }

        //证件类型
        if(StringUtils.isBlank(candidateSheet.getIdType())){
            rowErrs.add(ExcelLineErr.builder().line(2).msg("考生证件类型不能为空").build());
        } else {
            //证件类型约束校验
            if (!String.valueOf(candidateSheet.getIdType()).matches(idTypeRegex)) {
                rowErrs.add(ExcelLineErr.builder().line(2).msg("证件类型约束不符合").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getIdType().equals(temp.getIdType())){
                rowErrs.add(ExcelLineErr.builder().line(2).msg("考生证件类型存在不一致").build());
            }

        }
        //证件号码
        if(StringUtils.isBlank(candidateSheet.getIdNumber())){
            rowErrs.add(ExcelLineErr.builder().line(3).msg("考生证件号码不能为空").build());
        } else {
            //检查一下证件号码是否对
            if(CertificateTypeEnum.IDCARD.getType().equalsIgnoreCase(candidateSheet.getIdType())){
                String validate = IDCardUtil.IdCardValidate(candidateSheet.getIdNumber());
                //中国身份证
                if(!"YES".equalsIgnoreCase(validate)){
                    rowErrs.add(ExcelLineErr.builder().line(3).msg("考生证件号码:" + candidateSheet.getIdNumber() + " 不符合规范: " + validate).build());
                }
            } else if (CertificateTypeEnum.HONGKONGMACAOANDTAIWANIDCARD.getType().equalsIgnoreCase(candidateSheet.getIdType())){
                //港澳头
                if(!candidateSheet.getIdNumber().matches(Constant.HKMACAOTAWAN)){
                    rowErrs.add(ExcelLineErr.builder().line(3).msg("考生证件号码:" + candidateSheet.getIdNumber() + " 不符合规范").build());
                }
            }
        }
        //科目
        if(StringUtils.isBlank(candidateSheet.getSubject())){
            rowErrs.add(ExcelLineErr.builder().line(4).msg("考生科目不能为空").build());
        } else {
            String subject = "";
            if (ObjectUtil.isNotEmpty(temp)){
                subject = temp.getSubject();
            } else{
                subject = candidateSheet.getSubject();
            }
            StringBuilder sb = new StringBuilder();
            List<String> names = Arrays.asList(subject.split("#"));
            //判断是否有重复的
            if(!CollectionUtils.isEmpty(names) && names.size() > 1){
                int distinctCount = (int) names.stream().distinct().count();
                if(distinctCount != names.size()){
                    sb.append("考生科目:" + candidateSheet.getSubject() + "存在重复值").append("  ");
                }
            }

            if(StringUtils.isNotBlank(sb.toString())){
                rowErrs.add(ExcelLineErr.builder().line(4).msg(sb.toString()).build());
            }
        }
        if(StringUtils.isNotBlank(candidateSheet.getPhone())){
            boolean validPhone = StringUtil.isMobile(candidateSheet.getPhone());
            if (!validPhone) {
                //手机号格式不合法
                rowErrs.add(ExcelLineErr.builder().line(5).msg("手机号:" + candidateSheet.getPhone() + "格式不符合规定").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getPhone().equals(temp.getPhone())){
                rowErrs.add(ExcelLineErr.builder().line(5).msg("手机号存在不一致").build());
            }
        }
        if(StringUtils.isNotBlank(candidateSheet.getEmail())){
            if (!Validator.isEmail(candidateSheet.getEmail())) {
                //邮箱格式不合法
                rowErrs.add(ExcelLineErr.builder().line(6).msg("邮箱:" + candidateSheet.getEmail() + "格式不符合规定").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getEmail().equals(temp.getEmail())){
                rowErrs.add(ExcelLineErr.builder().line(6).msg("邮箱存在不一致").build());
            }
        }
        if(StringUtils.isNotBlank(candidateSheet.getBirthday())){
            //验证日期格式
            String [] split = candidateSheet.getBirthday().split("/");
            //日期分段长度
            final int birthdayBlockSize = 3;
            if(split.length == birthdayBlockSize){
                //年
                String year = split[0];
                //月
                String month = split[1].length() == 1?"0"+split[1]:split[1];
                //日
                String day = split[2].length() == 1?"0"+split[2]:split[2];
                candidateSheet.setBirthday(String.format("%s-%s-%s",year,month,day));
            }
            if (TextUtil.isNotDate(candidateSheet.getBirthday())) {
                rowErrs.add(ExcelLineErr.builder().line(7).msg("出生日期:" + candidateSheet.getBirthday() + "格式不符合规定").build());
            }
        }
        if(StringUtils.isNotBlank(candidateSheet.getSex())){
            //性别约束校验
            if (!String.valueOf(candidateSheet.getSex()).matches(sexRegex)) {
                rowErrs.add(ExcelLineErr.builder().line(7).msg("性别约束" + candidateSheet.getSex() + "不符合规定").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getSex().equals(temp.getSex())){
                rowErrs.add(ExcelLineErr.builder().line(7).msg("性别存在不一致").build());
            }
        }
        //最小区域编码
        if(StringUtils.isNotBlank(candidateSheet.getMinAreaLevel())){
            isSelfMinLevel.put(9,"true");
            if(Objects.isNull(districtMap.get(candidateSheet.getMinAreaLevel()))){
                rowErrs.add(ExcelLineErr.builder().line(9).msg("最小区域编码:" + candidateSheet.getMinAreaLevel() + ": 在区域编码中不存在或者不是最小区域编码").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getMinAreaLevel().equals(temp.getMinAreaLevel())){
                rowErrs.add(ExcelLineErr.builder().line(9).msg("最小区域编码存在不一致").build());
            }
        }

        //组织编码
        if(StringUtils.isNotBlank(candidateSheet.getDeptId())){
            if(Objects.isNull(orgMap.get(candidateSheet.getDeptId()))){
                rowErrs.add(ExcelLineErr.builder().line(9).msg("组织编码" + candidateSheet.getDeptId() + "在系统中不存在记录").build());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getDeptId().equals(temp.getDeptId())){
                rowErrs.add(ExcelLineErr.builder().line(9).msg("组织编码存在不一致").build());
            }
        }
        //作答方式
        if(StringUtils.isNotBlank(candidateSheet.getAnswerType())){
            //作答类型约束校验
            if (!String.valueOf(candidateSheet.getAnswerType()).matches(answerTypeRegex)) {
                rowErrs.add(ExcelLineErr.builder().line(10).msg("作答方式" + candidateSheet.getAnswerType() + "约束不符合").build());
            }
        }
        //语种类型
        if(StringUtils.isNotBlank(candidateSheet.getLanguage())){
            final String language = "1";
            if (!language.equals(candidateSheet.getLanguage())) {
                rowErrs.add(ExcelLineErr.builder().line(11).msg("语种类型" + candidateSheet.getLanguage() + "约束不符合").build());
            }
        }
        //自定义准考证号
        if(StringUtils.isNotBlank(candidateSheet.getAdmissionNumber())){
            isAdmissionNum.put(12,"true");
            if(selfAdmisson.contains(candidateSheet.getAdmissionNumber())){
                rowErrs.add(ExcelLineErr.builder().line(12).msg("考生准考证:" + candidateSheet.getAdmissionNumber() + " 与任务记录中已存在重复值").build());
            } else if (admissons.contains(candidateSheet.getAdmissionNumber())){
                rowErrs.add(ExcelLineErr.builder().line(12).msg("考生准考证:" + candidateSheet.getAdmissionNumber() + " 考试任务系统中存在重复值").build());
            }else {
                selfAdmisson.add(candidateSheet.getAdmissionNumber());
            }
            if(ObjectUtil.isNotEmpty(temp) && !candidateSheet.getAdmissionNumber().equals(temp.getAdmissionNumber())){
                rowErrs.add(ExcelLineErr.builder().line(9).msg("考生准考证存在不一致").build());
            }
        } else if (StringUtils.isBlank(candidateSheet.getAdmissionNumber()) && !isAdmissionNum.isEmpty()){
            rowErrs.add(ExcelLineErr.builder().line(12).msg("自定义准考证号要求全为空或者全部定义").build());
        }
        return rowErrs;
    }

    //处理自定义层级
    private void changeAreaSelf(ImportSelfAreaSheet district, Candidate candidate){
        //key areaCode value object
        if(district != null) {
            candidate.setProvince(district.getProvinceCode());
            candidate.setCity(district.getCityCode());
            candidate.setDistrict(district.getDistrictCode());
        }
    }

    /**
     * 验证考生科目并组装科目数据
     * @param candidateSheets
     * @param taskId
     * @return
     */
    private List<CandidateSubject> saveCandidateSubject(List<Subject> subjectList,List<ImportTaskCandidateSheet> candidateSheets, String taskId,
                                                           List<ImportTaskCandidateSheet> existedCandidateSubjectList,
                                                        Map<String, Subject> subjectMap) {
        //1.组装科目信息
        List<CandidateSubject> candidateSubjects = new ArrayList<>();
        Map<String,Subject> temp = new HashMap<>();
        if(CollectionUtil.isNotEmpty(subjectList)){
            temp = subjectList.stream().collect(Collectors.toMap(Subject::getSubjectName,
                    stu -> stu,
                    (key1 , key2) -> key2));
        }
        //2.遍历
        for(ImportTaskCandidateSheet sheet : candidateSheets){
            String candidateId = sheet.getId();
            String subjectCodes = sheet.getSubject();
            if(StringUtils.isBlank(subjectCodes)){
                continue;
            }
            //一次可以多个科目要处理
            String[] subjectCodeArray = subjectCodes.split("#");
            for(String subjectCode : subjectCodeArray){
                CandidateSubject subject = new CandidateSubject();
                subject.setId(UUIDUtils.newSortUUID());
                //查找已存在的考生数据，将已存在的考生id赋值给考生科目
                Optional<ImportTaskCandidateSheet> optional = existedCandidateSubjectList.stream()
                        .filter(ex -> ex.getIdNumber().equals(sheet.getIdNumber()))
                        .findFirst();
                if (optional.isPresent()) {
                    subject.setCandidateId(optional.get().getId());
                } else {
                    subject.setCandidateId(candidateId);
                }
                if(subjectMap.containsKey(subjectCode)){
                    subject.setSubjectId(subjectMap.get(subjectCode).getId());
                } else {
                    if(CollectionUtil.isNotEmpty(subjectList) && temp.containsKey(subjectCode)){
                        subject.setSubjectId(temp.get(subjectCode).getId());
                    }
                }

                subject.setTaskId(taskId);
                subject.setAnswerType(StringUtils.isBlank(sheet.getAnswerType()) ? 1 : Integer.parseInt(sheet.getAnswerType()));
                candidateSubjects.add(subject);
            }
        }
        return candidateSubjects;
    }

    //保存自定义层级关系
    private List<TaskCandidateArea>  changeSelfAreaToTaskCandidateArea(String taskId,List<ImportSelfAreaSheet> areaList){
        if(CollectionUtils.isEmpty(areaList)){
            return new ArrayList<>();
        }
        List<TaskCandidateArea> candidateAreas = new ArrayList<>(areaList.size() * 3);
        for (ImportSelfAreaSheet selfArea : areaList){
            //省
            TaskCandidateArea p = new TaskCandidateArea();
            p.setId(UUIDUtils.newSortUUID());
            p.setTaskId(taskId);
            p.setAreaId(selfArea.getProvinceCode());
            p.setName(selfArea.getProvinceName());
            p.setType(String.valueOf(2));

            //市
            TaskCandidateArea c = new TaskCandidateArea();
            c.setId(UUIDUtils.newSortUUID());
            c.setTaskId(taskId);
            c.setAreaId(selfArea.getCityCode());
            c.setName(selfArea.getCityName());
            c.setType(String.valueOf(3));
            c.setParentId(p.getAreaId());
            c.setParentIds(p.getAreaId());


            //区
            TaskCandidateArea d = new TaskCandidateArea();
            d.setId(UUIDUtils.newSortUUID());
            d.setTaskId(taskId);
            d.setAreaId(selfArea.getDistrictCode());
            d.setName(selfArea.getDistrictName());
            d.setType(String.valueOf(4));
            d.setParentId(c.getAreaId());
            d.setParentIds(p.getAreaId() + "," + c.getAreaId());

            candidateAreas.add(p);
            candidateAreas.add(c);
            candidateAreas.add(d);
        }
        return candidateAreas;
    }

    //读取excel
    private void readExcel(File file, List<ImportTaskCandidateSheet> importCandidates,
                           List<ImportTaskCandidateSheet> noRepeatCandidateSheets, List<ImportSelfAreaSheet> areaList, String taskId) {
        ExcelReader excelReader = EasyExcel.read(file).build();
        TaskCandidateListener candidateListener = new TaskCandidateListener(taskId);
        SelfLevelListener selfLevelListener = new SelfLevelListener();
        // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
        ReadSheet areSheet = EasyExcel.readSheet(0).head(ImportSelfAreaSheet.class).registerReadListener(selfLevelListener).build();
        ReadSheet placeRoomSheet = EasyExcel.readSheet(2).head(ImportTaskCandidateSheet.class).registerReadListener(candidateListener).build();
        // 这里注意 一定要把sheet1 sheet2 一起传进去，不然有个问题就是03版的excel 会读取多次，浪费性能
        excelReader.read(areSheet, placeRoomSheet);
        importCandidates.addAll(candidateListener.getImportTaskCandidates());
        noRepeatCandidateSheets.addAll(candidateListener.getImportNoRepeatTaskCandidates());
        areaList.addAll(selfLevelListener.getDataList());
    }

    /**
     * 构造自定义层级数据
     * @param areaSheetList
     */
    private List<ExcelRowErr> analysisSelfLevelExcelData(String taskId, List<ImportSelfAreaSheet> areaSheetList,
                                                        List<TaskCandidateArea> selfLevelList, Map<String, ImportSelfAreaSheet> districtMap){
        List<ExcelRowErr> row3Errs = new ArrayList<>();
        if (CollectionUtils.isEmpty(areaSheetList)) {
            return row3Errs;
        }

        List<ImportSelfAreaSheet> dataList = new ArrayList<>();

        List<TaskCandidateArea> db_data = iTaskCandidateAreaService.lambdaQuery()
                .eq(TaskCandidateArea::getTaskId, taskId).eq(TaskCandidateArea::getDelFlag,Boolean.FALSE).list();
        Map<String, String> db_map = new HashMap<>();
        if(!db_data.isEmpty()) {
            db_map.putAll(db_data.stream().collect(Collectors.toMap(TaskCandidateArea::getAreaId, TaskCandidateArea::getName,(k1,k2) -> k1)));
        }

        for (int i = 0; i < areaSheetList.size(); i++) {
            List<ExcelLineErr> lineErrs = new ArrayList<>();

            ImportSelfAreaSheet area = areaSheetList.get(i);
            if (StringUtils.isEmpty(area.getProvinceName())) {
                lineErrs.add(ExcelLineErr.builder().line(0).msg("省名称不能为空").build());
            }
            if (StringUtils.isEmpty(area.getProvinceCode())) {
                lineErrs.add(ExcelLineErr.builder().line(1).msg("省编码不能为空").build());
            }
            if (StringUtils.isEmpty(area.getCityName())) {
                lineErrs.add(ExcelLineErr.builder().line(2).msg("市名称不能为空").build());
            }
            if ( StringUtils.isEmpty(area.getCityCode())) {
                lineErrs.add(ExcelLineErr.builder().line(3).msg("市编码不能为空").build());
            }
            if (StringUtils.isEmpty(area.getDistrictName())) {
                lineErrs.add(ExcelLineErr.builder().line(4).msg("区县名称不能为空").build());
            }
            if (StringUtils.isEmpty(area.getDistrictCode())) {
                lineErrs.add(ExcelLineErr.builder().line(5).msg("区县编码不能为空").build());
            }
            if(lineErrs.size() > 0) {
                row3Errs.add(ExcelRowErr.builder().row(i).lineErrList(lineErrs).build());
                continue;
            }
            dataList.add(area);
            districtMap.put(area.getDistrictCode(), area);
        }

        if(dataList.size() == 0) {
            return row3Errs;
        }

        String countryCode = "0";
        //筛选省数据
        List<ImportSelfAreaSheet> provinceList = dataList.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ImportSelfAreaSheet::getProvinceCode))), ArrayList::new
                ));
        //筛选市数据
        List<ImportSelfAreaSheet> cityList = dataList.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ImportSelfAreaSheet::getCityCode))), ArrayList::new
                ));
        //筛选区县数据
        List<ImportSelfAreaSheet> districtList = dataList.stream()
                .collect(Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(ImportSelfAreaSheet::getDistrictCode))), ArrayList::new
                ));

        //组装省
        List<TaskCandidateArea> provinceAreaList = provinceList.stream().filter(item -> !db_map.containsKey(item.getProvinceCode())).map(province -> {
            TaskCandidateArea officeArea = new TaskCandidateArea();
            officeArea.setAreaId(province.getProvinceCode())
                    .setTaskId(taskId)
                    .setParentId("0")
                    .setParentIds("0")
                    .setName(province.getProvinceName())
                    .setType("2")
                    .setFullName(province.getProvinceName())
                    .setId(UUIDUtils.randomUUID());
            return officeArea;
        }).collect(Collectors.toList());
        selfLevelList.addAll(provinceAreaList);
        //组装市
        List<TaskCandidateArea> cityAreaList = cityList.stream().filter(item -> !db_map.containsKey(item.getCityCode())).map(city -> {
            TaskCandidateArea officeArea = new TaskCandidateArea();
            officeArea.setAreaId(city.getCityCode())
                    .setTaskId(taskId)
                    .setParentId(city.getProvinceCode())
                    .setParentIds(countryCode +  "," + city.getProvinceCode())
                    .setName(city.getCityName())
                    .setType("3")
                    .setFullName(city.getProvinceName()  + "/" + city.getCityName())
                    .setId(UUIDUtils.randomUUID());
            return officeArea;
        }).collect(Collectors.toList());
        selfLevelList.addAll(cityAreaList);
        //组装区县
        List<TaskCandidateArea> districtAreaList = districtList.stream().filter(item -> !db_map.containsKey(item.getDistrictCode())).map(district -> {
            TaskCandidateArea officeArea = new TaskCandidateArea();
            officeArea.setAreaId(district.getDistrictCode())
                    .setTaskId(taskId)
                    .setParentId(district.getCityCode())
                    .setParentIds(countryCode + "," + district.getProvinceCode() + "," + district.getCityCode())
                    .setName(district.getDistrictName())
                    .setType("4")
                    .setFullName(district.getProvinceName()   + "/" + district.getCityName()   + "/" +  district.getDistrictName())
                    .setId(UUIDUtils.randomUUID());
            return officeArea;
        }).collect(Collectors.toList());
        selfLevelList.addAll(districtAreaList);
        return row3Errs;
    }

    /**
     * 考试数据分拣并保存
     * @param listener
     * @param taskId
     * @return
     */
    @Override
    public void saveImportCandidate(TaskCandidateListener listener, String taskId) {
        //0.获取去重表格数据
        List<ImportTaskCandidateSheet> noRepeatCandidateSheets = listener.getImportNoRepeatTaskCandidates();
        //获取任务下已存在的考生科目数据
        List<ImportTaskCandidateSheet> existedCandidateSubjectList = baseMapper.getExistedCandidateSubject(taskId);
        //1.查询数据库中当前导入的考生数据证件号是否存在重复（证件号码、科目均相同，则视为重复考生）
        String repeatCandidates = noRepeatCandidateSheets.stream()
                .filter(candidate -> existedCandidateSubjectList.stream()
                        .anyMatch(ex -> ex.getIdNumber().equals(candidate.getIdNumber())
                                && ex.getSubject().equals(candidate.getSubject())))
                .collect(Collectors.toList())
                .stream()
                .map(ImportTaskCandidateSheet::getIdNumber)
                .collect(Collectors.joining(","));
        //2.数据库中存在重复 则告诉客户端 重复的证件id;
        if (StringUtils.isNotEmpty(repeatCandidates)) {
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_ID_NUMBER_DUPLICATE.getCode(),
                    repeatCandidates);
        }
        //去除已经存在数据库中的考生基础信息，防止重复保存考生基础信息
        noRepeatCandidateSheets = noRepeatCandidateSheets.stream()
                .filter(candidate -> existedCandidateSubjectList.stream()
                        .noneMatch(ex -> ex.getIdNumber().equals(candidate.getIdNumber())))
                .collect(Collectors.toList());
        //校验导入考生的区域信息与任务中的考生区域信息是否匹配
        List<TaskCandidateArea> candidateDistrictList = iTaskCandidateAreaService.lambdaQuery()
                .select(TaskCandidateArea::getAreaId, TaskCandidateArea::getParentIds)
                .eq(TaskCandidateArea::getTaskId, taskId)
                .eq(TaskCandidateArea::getType, String.valueOf(CodeEnum.AREA_TYPE_DISTRICT.getCode()))
                .eq(TaskCandidateArea::getDelFlag, false)
                .list();
        if (!CollectionUtils.isEmpty(candidateDistrictList)) {
            //系统中存在考生区域数据时才校验导入考生的区域信息与任务中的考生区域信息是否匹配
            String areaUnmatchedCandidates = noRepeatCandidateSheets.stream()
                    .filter(candidate -> {
                        if (StrUtil.isAllBlank(candidate.getProvince(),
                                candidate.getCity(), candidate.getDistrict())) {
                            //区域信息全为空的考生不校验与系统中的考生区域信息是否匹配
                            return false;
                        }
                        //校验区县信息是否正确
                        Optional<TaskCandidateArea> optional = candidateDistrictList.stream()
                                .filter(candidateDistrict -> candidateDistrict.getAreaId().equals(candidate.getDistrict()))
                                .findFirst();
                        if (optional.isPresent()) {
                            //区县信息正确，则继续校验省市信息是否正确
                            List<String> parentIdList = Arrays.asList(optional.get()
                                    .getParentIds().split(","));
                            //省信息或市信息不正确，则视为区域信息不正确
                            return (!parentIdList.contains(candidate.getCity()))
                                    || (!parentIdList.contains(candidate.getProvince()));
                        }
                        //区县信息不正确
                        return true;
                    }).collect(Collectors.toList())
                    .stream()
                    .map(ImportTaskCandidateSheet::getIdNumber)
                    .collect(Collectors.joining(","));
            if (StringUtils.isNotEmpty(areaUnmatchedCandidates)) {
                throw new KqException(ResponseCode.IMPORT_CANDIDATE_AREA_UNMATCHED.getCode(),
                        areaUnmatchedCandidates);
            }
        }
        //3.获取原子表格数据
        List<ImportTaskCandidateSheet> candidateSheets = listener.getImportTaskCandidates();
        //4.验证科目并返回组装好的考生科目数据
        List<CandidateSubject> candidateSubjects = this.convertCandidateSubject(candidateSheets,
                taskId, existedCandidateSubjectList);
        //组装自定义编排数据
        List<CustomArrange> customArrangeList = this.convertCustomArrange(taskId, candidateSheets,
                existedCandidateSubjectList);
        //5.组装考生入库数据
        List<Candidate> candidates = new ArrayList<>();
        noRepeatCandidateSheets.forEach(sheet -> {
            Candidate candidate = new Candidate();
            BeanUtils.copyProperties(sheet, candidate);
            candidate.setSex(Integer.valueOf(sheet.getSex()));
            candidate.setIdType(Integer.valueOf(sheet.getIdType()));
            candidates.add(candidate);
        });
        //6.拆分数据进行导入
        this.saveCandidates(candidates);
        //7.拆分批量保存考生科目
        this.saveCandidatesSubject(candidateSubjects);
        //保存考生准考证号数据
        this.saveCandidateAdmissionNumber(customArrangeList);
        //保存考生准考证号数据
        this.saveCandidateAdmissionNumber(customArrangeList);
        //8.修改task中的报名任务id
        iTaskService.lambdaUpdate()
                .eq(Task::getId, taskId)
                .set(Task::getTaskBmId, "0")
                .update();
    }

    /**
     * 组装自定义编排数据
     * @param taskId 考试任务id
     * @param importTaskCandidateSheetList 导入的原始数据
     * @param existedCandidateSubjectList 已存在的考生数据
     * @return 自定义编排数据
     */
    private List<CustomArrange> convertCustomArrange(String taskId, List<ImportTaskCandidateSheet> importTaskCandidateSheetList,
                                                     List<ImportTaskCandidateSheet> existedCandidateSubjectList) {

        //获取已存在的考生自定义编排数据
        List<CustomArrangeDTO> customArrangeDTOList = iCustomArrangeService.listExistedByTaskId(taskId);

        //组装自定义批次、考场数据
        Map<String, List<TimeRoomDTO>> customArrangeTimeRoomMap = this.convertCustomArrangeTimeRoom(taskId,
                importTaskCandidateSheetList, customArrangeDTOList);

        //组装自定义准考证号数据
        Map<String, String> customAdmissionNumberMap = this.convertCustomAdmissionNumber(taskId,
                importTaskCandidateSheetList, customArrangeDTOList);

        //获取与导入的考生相匹配的自定义编排数据
        List<CustomArrangeDTO> existedCustomArrangeList = new ArrayList<>();
        importTaskCandidateSheetList.stream()
                .collect(Collectors.groupingBy(ImportTaskCandidateSheet::getIdNumber))
                .keySet()
                .forEach(idNumber -> {
                    List<CustomArrangeDTO> list = customArrangeDTOList.stream()
                            .filter(customArrangeDTO -> customArrangeDTO.getIdNumber().equals(idNumber))
                            .collect(Collectors.toList());
                    existedCustomArrangeList.addAll(list);
                });

        List<CustomArrange> customArrangeList = new ArrayList<>();

        //组装数据
        if (CollectionUtils.isEmpty(existedCustomArrangeList)) {
            //无已存在的自定义编排数据，直接新增
            //处理自定义批次、考场数据
            customArrangeTimeRoomMap.forEach((idNumber, timeRoomDTOList) -> {
                List<CustomArrange> list = timeRoomDTOList.stream().map(timeRoom -> {
                    CustomArrange customArrange = new CustomArrange();
                    //设置考试任务id
                    customArrange.setTaskId(taskId);
                    //设置考生id
                    String candidateId = this.getCandidateIdByIdNumber(existedCandidateSubjectList,
                            importTaskCandidateSheetList, idNumber);
                    customArrange.setCandidateId(candidateId);
                    //设置批次id
                    customArrange.setTimeId(timeRoom.getTimeId());
                    //设置考场编号
                    customArrange.setRoomCode(timeRoom.getRoomCode());
                    return customArrange;
                }).collect(Collectors.toList());
                customArrangeList.addAll(list);
            });
            //处理自定义准考证数据
            customAdmissionNumberMap.forEach((idNumber, admissionNumber) -> {
                //获取考生id
                String candidateId = this.getCandidateIdByIdNumber(existedCandidateSubjectList,
                        importTaskCandidateSheetList, idNumber);
                boolean flag = false;
                for (CustomArrange customArrange : customArrangeList) {
                    if (customArrange.getCandidateId().equals(candidateId)) {
                        customArrange.setAdmissionNumber(admissionNumber);
                        flag = true;
                    }
                }
                if (!flag) {
                    CustomArrange customArrange = new CustomArrange();
                    //设置考试任务id
                    customArrange.setTaskId(taskId);
                    //设置考生id
                    customArrange.setCandidateId(candidateId);
                    //设置准考证号
                    customArrange.setAdmissionNumber(admissionNumber);
                    customArrangeList.add(customArrange);
                }
            });

        } else {
            //有已存在的自定义编排数据，按实际情况处理

            //导入的数据中有自定义编排数据、无自定义准考证号数据
            if (!CollectionUtils.isEmpty(customArrangeTimeRoomMap)
                    && CollectionUtils.isEmpty(customAdmissionNumberMap)) {
                //完善自定义编排数据
                customArrangeTimeRoomMap.forEach((idNumber, timeRoomDTOList) -> {
                    //获取考生id
                    String candidateId = this.getCandidateIdByIdNumber(existedCandidateSubjectList,
                            importTaskCandidateSheetList, idNumber);
                    List<CustomArrange> list = timeRoomDTOList.stream().map(timeRoom -> {
                        CustomArrange customArrange = new CustomArrange();
                        //设置考试任务id
                        customArrange.setTaskId(taskId);
                        //设置考生id
                        customArrange.setCandidateId(candidateId);
                        //设置批次id
                        customArrange.setTimeId(timeRoom.getTimeId());
                        //设置考场编号
                        customArrange.setRoomCode(timeRoom.getRoomCode());
                        //设置准考证号
                        String admissionNumber = existedCustomArrangeList.stream()
                                .filter(ex -> ex.getIdNumber().equals(idNumber))
                                .findFirst()
                                .map(CustomArrangeDTO::getAdmissionNumber)
                                .orElse(null);
                        customArrange.setAdmissionNumber(admissionNumber);
                        return customArrange;
                    }).collect(Collectors.toList());
                    customArrangeList.addAll(list);
                });
            } else if (CollectionUtils.isEmpty(customArrangeTimeRoomMap)
                    && !CollectionUtils.isEmpty(customAdmissionNumberMap)) {
                //导入的数据中有自定义准考证号数据、无自定义编排数据
                customAdmissionNumberMap.forEach((idNumber, admissionNumber) -> {
                    //获取已存在的自定义编排数据
                    List<CustomArrangeDTO> existedList = customArrangeDTOList.stream()
                            .filter(ex -> ex.getIdNumber().equals(idNumber))
                            .collect(Collectors.toList());
                    if (CollectionUtils.isEmpty(existedList)) {
                        //当前考生无已存在的自定义编排数据
                        CustomArrange customArrange = new CustomArrange();
                        customArrange.setTaskId(taskId)
                                .setCandidateId(StringUtils.EMPTY)
                                .setAdmissionNumber(StringUtils.EMPTY);
                        customArrangeList.add(customArrange);
                    } else {
                        //当前导入的考生有已存在的自定义编排数据
                        List<CustomArrange> list = existedList.stream().map(ex -> {
                            //设置准考证号
                            ex.setAdmissionNumber(admissionNumber);
                            return (CustomArrange) ex;
                        }).collect(Collectors.toList());
                        customArrangeList.addAll(list);
                    }
                });
            }
        }
        if (!CollectionUtils.isEmpty(customArrangeList)) {
            List<String> customArrangeIdList = existedCustomArrangeList.stream()
                    .map(CustomArrangeDTO::getId)
                    .collect(Collectors.toList());
            iCustomArrangeService.removeByIds(customArrangeIdList);
        }
        return customArrangeList;
    }

    /**
     * 根据证件号获取考生id
     * @param existedCandidateSubjectList
     * @param importTaskCandidateSheetList
     * @param idNumber
     * @return
     */
    private String getCandidateIdByIdNumber(List<ImportTaskCandidateSheet> existedCandidateSubjectList,
                                            List<ImportTaskCandidateSheet> importTaskCandidateSheetList, String idNumber) {
        //设置考生id
        Optional<ImportTaskCandidateSheet> optional = existedCandidateSubjectList.stream()
                .filter(ex -> ex.getIdNumber().equals(idNumber))
                .findFirst();
        if (optional.isPresent()) {
            //使用已存在的考生id
            return optional.get().getId();
        } else {
            //使用新增的考生id
            return importTaskCandidateSheetList.stream()
                    .filter(imp -> idNumber.equals(imp.getIdNumber()))
                    .findFirst()
                    .map(ImportTaskCandidateSheet::getId)
                    .orElseThrow(() -> new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                            "根据证件号获取考生id错误"));
        }
    }

    /**
     * 组装自定义批次、考场数据
     * @param taskId
     * @param importTaskCandidateSheetList
     * @param existCustomArrange
     * @return
     */
    private Map<String, List<TimeRoomDTO>> convertCustomArrangeTimeRoom(String taskId, List<ImportTaskCandidateSheet> importTaskCandidateSheetList,
                                                                        List<CustomArrangeDTO> existCustomArrange) {
        //校验是否存在自定义编排数据不完全的情况（即：要不就所有考生都有自定义编排数据，要不就所有考生都没有自定义编排数据）
        //所有数据按照考生证件号分组
        Map<String, List<ImportTaskCandidateSheet>> idNumberCandidateMap = importTaskCandidateSheetList.stream()
                .collect(Collectors.groupingBy(ImportTaskCandidateSheet::getIdNumber));
        //获取无自定义编排数据的考生
        Map<String, List<ImportTaskCandidateSheet>> withoutCustomMap = idNumberCandidateMap.entrySet()
                .stream()
                .filter(entry -> entry.getValue().stream()
                        .anyMatch(imp -> StrUtil.isAllBlank(imp.getRoomCode(), imp.getTimeCode())))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        if (idNumberCandidateMap.size() == withoutCustomMap.size()) {
            //所有考生都没有自定义编排数据
            return new HashMap<>();
        }
        //获取自定义编排数据不全的考生，校验是否存在自定义编排数据不全的考生
        Map<String, List<ImportTaskCandidateSheet>> lackCustomMap = idNumberCandidateMap.entrySet()
                .stream()
                .filter(entry -> entry.getValue().stream()
                        .anyMatch(imp -> StrUtil.hasBlank(imp.getRoomCode(), imp.getTimeCode())))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        if (!CollectionUtils.isEmpty(lackCustomMap)) {
            //存在自定义编排数据不全的考生
            String lackCustomCandidateIdNumbers = String.join(",", lackCustomMap.keySet());
            //以下证件号码对应的考生自定义编排数据（自定义考场编号、自定义批次编号）不全
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生自定义编排数据（自定义考场编号、自定义批次编号）不全: "
                    + lackCustomCandidateIdNumbers);
        }
        //所有考生都存在自定义编排数据，开始校验自定义编排数据合理性
        //获取批次-科目数据
        List<TimeSubjectDTO> timeSubjectDTOList = iTimeSubjectService.getTimeSubjectDTO(taskId);
        //校验批次编号是否存在
        String wrongTimeCode = importTaskCandidateSheetList.stream()
                .filter(imp -> timeSubjectDTOList.stream()
                        .noneMatch(timeSubject -> timeSubject.getTimeCode().equals(new Integer(imp.getTimeCode()))))
                .map(ImportTaskCandidateSheet::getIdNumber)
                .collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(wrongTimeCode)) {
            //以下证件号码对应的考生批次编号不存在
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生批次编号不存在: "
                    + wrongTimeCode);
        }
        //校验科目编号与批次编号的对应关系是否正确
        //过滤出科目编号与批次编号对应关系不正确的考生数据
        String wrongTimeSubject = importTaskCandidateSheetList.stream().filter(imp -> {
                    String subjectCode = imp.getSubject();
                    String timeCode = imp.getTimeCode();
                    //根据批次编号获取批次对应的科目数据
                    List<SubjectDTO> subjectDTOList = timeSubjectDTOList.stream()
                            .filter(timeSubject -> timeSubject.getTimeCode().equals(new Integer(timeCode)))
                            .findFirst()
                            .map(TimeSubjectDTO::getSubjectDTOList)
                            .orElseThrow(() -> new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                                    "根据批次编号获取批次对应的科目数据失败"));
                    //查找科目数据中是否存在指定科目编号
                    return subjectDTOList.stream()
                            .noneMatch(subject -> subject.getSubjectCode().equals(subjectCode));
                })
                .map(ImportTaskCandidateSheet::getIdNumber)
                .collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(wrongTimeSubject)) {
            //以下证件号码对应的考生科目编号与批次编号对应关系不正确
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生科目编号与批次编号对应关系不正确: "
                    + wrongTimeSubject);
        }
        //校验是否存在已有自定义编排数据的考生
        String hasCustom = idNumberCandidateMap.keySet().stream()
                .filter(idNumber -> existCustomArrange.stream().anyMatch(exist -> {
                    if (exist.getIdNumber().equals(idNumber)) {
                        return StringUtils.isNotBlank(exist.getTimeId()) || !ObjectUtils.isEmpty(exist.getRoomCode());
                    }
                    return false;
                })).collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(hasCustom)) {
            //以下证件号码对应的考生已有自定义编排数据
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生已有自定义编排数据: "
                    + hasCustom);
        }
        //组装自定义编排数据
        return idNumberCandidateMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry ->
                entry.getValue().stream().map(candidate -> {
                    //根据批次编号获取批次id
                    String timeCode = candidate.getTimeCode();
                    String timeId = timeSubjectDTOList.stream()
                            .filter(timeSubject -> timeSubject.getTimeCode().equals(new Integer(timeCode)))
                            .findFirst()
                            .map(TimeSubjectDTO::getTimeId)
                            .orElseThrow(() -> new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                                    "根据批次编号获取批次id失败"));
                    //设置批次id、考场编号
                    TimeRoomDTO timeRoomDTO = new TimeRoomDTO();
                    timeRoomDTO.setTimeId(timeId);
                    timeRoomDTO.setRoomCode(new Integer(candidate.getRoomCode()));
                    return timeRoomDTO;
                }).collect(Collectors.toList())));
    }

    /**
     * 组装自定义准考证号数据
     * @param taskId
     * @param importTaskCandidateSheetList
     * @param customArrangeDTOList
     * @return
     */
    private Map<String, String> convertCustomAdmissionNumber(String taskId, List<ImportTaskCandidateSheet> importTaskCandidateSheetList,
                                                             List<CustomArrangeDTO> customArrangeDTOList) {
        //将考生数据按照证件号分组，String为证件号、Set<String>为准考证号的Set
        //正常来说准考证号的Set内只能有1个元素，若存在多个元素则说明同个考生具有不同的准考证号，当前版本暂不支持导入同个考生不同准考证号的数据
        Map<String, Set<String>> candidateAdmissionNumberMap = importTaskCandidateSheetList.stream()
                .collect(Collectors.groupingBy(ImportTaskCandidateSheet::getIdNumber,
                        Collectors.mapping(ImportTaskCandidateSheet::getAdmissionNumber, Collectors.toSet())));
        //获取没有准考证号的考生
        Map<String, Set<String>> withoutAdmissionNumberMap = candidateAdmissionNumberMap.entrySet().stream()
                .filter(entry -> CharSequenceUtil.hasBlank(entry.getValue().toArray(new String[0])))
                .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
        //是否所有考生都没有准考证号
        if (candidateAdmissionNumberMap.size() == withoutAdmissionNumberMap.size()) {
            //所有考生都没有准考证号
            return new HashMap<>();
        }
        //校验是否存在考生准考证号不完全的情况（存在无准考证号的考生，且无准考证号的考生数不等于考生总数。即：要不就所有考生都有准考证号，要不就所有考生都没有准考证号）
        if (!CollectionUtils.isEmpty(withoutAdmissionNumberMap)) {
            //存在考生准考证号不完全的情况
            String withoutAdmissionNumberCandidateIdNumbers = String.join(",", withoutAdmissionNumberMap.keySet());
            //以下证件号码对应的考生无准考证号
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生无准考证号: "
                    + withoutAdmissionNumberCandidateIdNumbers);
        }
        //获取准考证号不唯一的考生的证件号
        String notUniqueCandidateIdNumbers = candidateAdmissionNumberMap.entrySet().stream()
                .filter(entry -> entry.getValue().size() != 1)
                .map(Map.Entry::getKey)
                .collect(Collectors.joining(","));
        //校验是否存在同个考生准考证号不同的情况
        if (StringUtils.isNotBlank(notUniqueCandidateIdNumbers)) {
            //以下证件号码对应的同个考生存在准考证号不唯一的情况
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的同个考生存在准考证号不唯一的情况: "
                    + notUniqueCandidateIdNumbers);
        }
        //获取考生证件号与准考证号的映射关系，key为证件号、value为准考证号
        Map<String, String> idAdmissionNumberMap = candidateAdmissionNumberMap.entrySet().stream()
                .collect(Collectors.toMap(Map.Entry::getKey, entry -> entry.getValue()
                        .stream()
                        .findFirst()
                        .orElseThrow(() -> new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                                "获取考生证件号与准考证号的映射关系时，获取准考证号错误"))
                ));
        //校验所有考生的准考证号是否唯一
        String duplicatedIdNumbers = idAdmissionNumberMap.entrySet().stream()
                .filter(entry -> idAdmissionNumberMap.entrySet().stream().anyMatch(e -> {
                    if (!e.getKey().equals(entry.getKey())) {
                        return e.getValue().equals(entry.getValue());
                    }
                    return false;
                }))
                .map(Map.Entry::getKey)
                .collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(duplicatedIdNumbers)) {
            //以下证件号码对应的不同考生存在相同准考证号的情况
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的不同考生存在相同准考证号的情况: "
                    + duplicatedIdNumbers);
        }
        //校验是否存在已有准考证号的考生
        String existedAdmissionNumberIds = idAdmissionNumberMap.keySet().stream()
                .filter(k -> customArrangeDTOList.stream().anyMatch(e -> {
                    if (e.getIdNumber().equals(k)) {
                        return StringUtils.isNotBlank(e.getAdmissionNumber());
                    }
                    return false;
                })).collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(existedAdmissionNumberIds)) {
            //以下证件号码对应的考生已有准考证号
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生已有准考证号: "
                    + existedAdmissionNumberIds);
        }
        //获取已存在的准考证号
        List<String> existedAdmissionNumber = iTimeRoomCandidateService.listAdmissionNumberByTaskId(taskId);
        List<String> existedCustomAdmissionNumber = customArrangeDTOList.stream()
                .map(CustomArrangeDTO::getAdmissionNumber)
                .collect(Collectors.toList())
                .stream()
                .distinct()
                .collect(Collectors.toList());
        existedAdmissionNumber.addAll(existedCustomAdmissionNumber);
        //校验是否存在准考证号与现有考生重复的数据
        String repeatAdmissionNumber = idAdmissionNumberMap.entrySet().stream()
                .filter(entry -> existedAdmissionNumber.contains(entry.getValue()))
                .map(Map.Entry::getKey)
                .collect(Collectors.joining(","));
        if (StringUtils.isNotBlank(repeatAdmissionNumber)) {
            //以下证件号码对应的考生与现有考生的准考证号重复
            throw new BusinessLogicException(ProgressConstant.RESPONSE_CODE_BUSINESS_ERROR, "以下证件号码对应的考生与现有考生的准考证号重复: "
                    + repeatAdmissionNumber);
        }
        return idAdmissionNumberMap;
    }

    /**
     * 组装自定义准考证号数据
     * @param taskId
     * @param sheets
     * @return
     */
    private List<CustomArrange> convertCustomAdmissionNumber(String taskId, List<ImportTaskCandidateSheet> sheets){
        if(CollectionUtils.isEmpty(sheets)){
            return new ArrayList<>();
        }
        List<CustomArrange> cas = new ArrayList<>(sheets.size());
        for(ImportTaskCandidateSheet sheet : sheets){
            CustomArrange ca = new CustomArrange();
            cas.add(ca);
            ca.setAdmissionNumber(sheet.getAdmissionNumber());
            ca.setTaskId(taskId);
            ca.setCandidateId(sheet.getId());
        }
        return cas;
    }

    /**
     * 保存考生准考证号数据
     * @param customArrangeList 考生准考证号数据
     */
    private void saveCandidateAdmissionNumber(List<CustomArrange> customArrangeList) {
        //1.分片大小
        int blockSize = 1000;
                //2.数据长度
        int dataSize = customArrangeList.size();
        //2.分片大小不超过1000直接批量插入
        if (customArrangeList.size() > 0) {
            if (blockSize >= dataSize) {
                if (!iCustomArrangeService.saveBatch(customArrangeList)) {
                    //3.出错抛出异常便于回滚
                    throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                            "导入失败");
                }
            } else {
                //4.获取次数
                int limit = (dataSize + blockSize - 1) / blockSize;
                //5.执行切割并插入
                Stream.iterate(0 , n -> n + 1).limit(limit).forEach(a -> {
                    //6.获取分片数据
                    List<CustomArrange> block = customArrangeList.stream()
                            .skip(a * blockSize)
                            .limit(blockSize)
                            .collect(Collectors.toList());
                    if (!iCustomArrangeService.saveBatch(block)) {
                        //3.出错抛出异常便于回滚
                        throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                                "导入失败");
                    }
                });
            }
        }
    }

    /**
     * 数据切割并保存
     * @param candidates
     */
    private void saveCandidates(List<Candidate> candidates) {
        //1.分片大小
        int blockSize = 1000,
            //2.数据长度
            dataSize = candidates.size();
        //2.分片大小不超过1000直接批量插入
        if (candidates.size() > 0) {
            if (blockSize >= dataSize) {
                if (!this.saveBatch(candidates)) {
                    //3.出错抛出异常便于回滚
                    throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_FAIL.getCode(),
                            ResponseCode.ERROR_BATCH_IMPORT_FAIL.getMsg());
                }
            } else {
                //4.获取次数
                int limit = (dataSize + blockSize -1) / blockSize;
                //5.执行切割并插入
                Stream.iterate(0 , n -> n + 1).limit(limit).forEach(a -> {
                    //6.获取分片数据
                    List<Candidate> block = candidates.stream()
                            .skip(a * blockSize)
                            .limit(blockSize)
                            .collect(Collectors.toList());
                    if (!this.saveBatch(block)) {
                        //3.出错抛出异常便于回滚
                        throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_FAIL.getCode(),
                                ResponseCode.ERROR_BATCH_IMPORT_FAIL.getMsg());
                    }
                });
            }
        }
    }


    /**
     * 验证考生科目并组装科目数据
     * @param candidateSheets
     * @param taskId
     * @return
     */
    private List<CandidateSubject> convertCandidateSubject(List<ImportTaskCandidateSheet> candidateSheets, String taskId,
                                                           List<ImportTaskCandidateSheet> existedCandidateSubjectList) {
        //1.获取导入数据中的所有科目
        //2.科目id去重
        Set<String> subCodes= candidateSheets.stream()
                .map(ImportTaskCandidateSheet::getSubject)
                .collect(Collectors.toSet());
        //3.根据去重后的考生科目id获取科目列表
        List<Subject> subjects = iSubjectService.listByCodes(taskId, subCodes);
        //4.科目转map 便于后面组装数据
        Map<String, Subject> subjectMap = subjects.stream()
                .collect(Collectors.toMap(Subject::getCode, Function.identity()));
        //5.所有科目不存在
        if (CollectionUtils.isEmpty(subjects)) {
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_SUBJECT_ID_NON_EXISTENT.getCode(),
                    Joiner.on(",").join(subCodes));
        }
        //6.科目中有不存在的科目id
        if (subCodes.size() != subjects.size()) {
            List<String> notExistSubjectCodes = new ArrayList<>();
            subCodes.forEach(code -> {
                if (TextUtil.isNull(subjectMap.get(code))) {
                    notExistSubjectCodes.add(code);
                }
            });
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_SUBJECT_ID_NON_EXISTENT.getCode(),
                    Joiner.on(",").join(notExistSubjectCodes));
        }
        //7.组装科目信息
        List<CandidateSubject> candidateSubjects = new ArrayList<>();
        //8.统计考生科目信息
        Map<String, Integer> subjectReport = new HashMap<>(1);
        //9.科目重复考生记录
        List<String> repeatSubjectCandidate = new ArrayList<>(2);
        //10.遍历
        candidateSheets.forEach(sheet -> {
            String candidateId = sheet.getId(),
                    subjectCode = sheet.getSubject(),
                    reportKey = sheet.getIdNumber() + subjectCode;
            //11.获取记数
            Integer subjectCount = subjectReport.get(reportKey);
            if (TextUtil.isNull(subjectCount)) {
                //12.保存记数
                subjectReport.put(reportKey, 1);
                CandidateSubject subject= new CandidateSubject();
                subject.setId(UUIDUtils.newSortUUID());
                //查找已存在的考生数据，将已存在的考生id赋值给考生科目
                Optional<ImportTaskCandidateSheet> optional = existedCandidateSubjectList.stream()
                        .filter(ex -> ex.getIdNumber().equals(sheet.getIdNumber()))
                        .findFirst();
                if (optional.isPresent()) {
                    subject.setCandidateId(optional.get().getId());
                } else {
                    subject.setCandidateId(candidateId);
                }
                subject.setSubjectId(subjectMap.get(subjectCode).getId());
                subject.setTaskId(taskId);
                subject.setAnswerType(Integer.valueOf(sheet.getAnswerType()));
                candidateSubjects.add(subject);
            } else {
                //13.记录重复科目重复的考生
                repeatSubjectCandidate.add(sheet.getIdNumber());
            }
        });
        //14.如果有重复记录则返回给前端提示
        if (TextUtil.isNotNull(repeatSubjectCandidate)) {
            throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_CANDIDATE_SUBJECT_REPEAT.getCode(),
                    Joiner.on(",").join(repeatSubjectCandidate));
        }
        return candidateSubjects;
    }

    /**
     * 批量保存考生科目信息
     * @param candidateSubjects
     */
    private void saveCandidatesSubject(List<CandidateSubject> candidateSubjects) {
        //数据大小和分片长度
        int dataSize = candidateSubjects.size(),
            blockSize = 1000;
        //如果大<=1000就一次插入，否则分段插入
        if (blockSize >= dataSize) {
            if (!iCandidateSubjectService.saveBatch(candidateSubjects)) {
                throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_READ_FILE_FAIL.getCode(),
                        "导入失败");
            }
        } else {
            //4.获取次数
            int limit = (dataSize + blockSize -1) / blockSize;
            //5.执行切割并插入
            Stream.iterate(0 , n -> n + 1).limit(limit).forEach(a -> {
                //6.获取分片数据
                List<CandidateSubject> block = candidateSubjects.stream()
                        .skip(a * blockSize)
                        .limit(blockSize)
                        .collect(Collectors.toList());
                if (!iCandidateSubjectService.saveBatch(block)) {
                    throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_FAIL.getCode(),
                            "导入失败");
                }
            });
        }
    }

    @Override
    public List<Candidate> listByTaskId(String taskId) {
        QueryWrapper<Candidate> queryWrapper = new QueryWrapper<>();
        queryWrapper.lambda()
                .eq(Candidate::getTaskId, taskId)
                .eq(Candidate::getDelFlag, false);
        return list(queryWrapper);
    }

    @Override
    public void checkCandidateAreaThrow(String taskId) {
        Integer count = lambdaQuery()
                .eq(Candidate::getTaskId,taskId)
                .isNull(Candidate::getDistrict)
                .eq(Candidate::getDelFlag,false)
                .count();
        if (count > 0) {
            throw new KqException(ResponseCode.CANDIDATE_AREA_NO_DISTRICT.getCode(),ResponseCode.CANDIDATE_AREA_NO_DISTRICT.getMsg());
        }
    }

    @Override
    public void deleteByTaskId(String taskId) {
        //删除考生表
        this.lambdaUpdate()
                .eq(Candidate::getTaskId, taskId)
                .eq(Candidate::getDelFlag, false)
                .set(Candidate::getDelFlag, true)
                .update();
        //删除考生照片表
        iCandidatePhotoService.lambdaUpdate()
                .eq(CandidatePhoto::getTaskId, taskId)
                .eq(CandidatePhoto::getDelFlag, false)
                .set(CandidatePhoto::getDelFlag, true)
                .update();
        //删除考生科目表
        iCandidateSubjectService.lambdaUpdate()
                .eq(CandidateSubject::getTaskId, taskId)
                .eq(CandidateSubject::getDelFlag, false)
                .set(CandidateSubject::getDelFlag, true)
                .update();
        //删除考生区域表
        iTaskCandidateAreaService.lambdaUpdate()
                .eq(TaskCandidateArea::getTaskId, taskId)
                .eq(TaskCandidateArea::getDelFlag, false)
                .set(TaskCandidateArea::getDelFlag, true)
                .update();
        //删除考生编排表
        iTimeRoomCandidateService.lambdaUpdate()
                .eq(TimeRoomCandidate::getTaskId, taskId)
                .eq(TimeRoomCandidate::getDelFlag, false)
                .set(TimeRoomCandidate::getDelFlag, true)
                .update();
        // 清空自定义准考证数据
        iCustomArrangeService.lambdaUpdate()
                .eq(CustomArrange::getTaskId, taskId)
                .eq(CustomArrange::getDelFlag, false)
                .set(CustomArrange::getDelFlag, true)
                .update();;
    }

    @Override
    public Candidate getCandidateByBmStudentId(String taskId,String bmStudentId) {

        Candidate candidate = this.lambdaQuery()
                .eq(Candidate::getTaskId,taskId)
                .eq(Candidate::getBmStudentId,bmStudentId)
                .eq(Candidate::getDelFlag,false)
                .select(Candidate::getId,Candidate::getIdNumber)
                .last("limit 1")
                .one();

        return candidate;
    }

    @Override
    public Candidate getCandidateByCandidateId(String taskId, String candidateId) {
        return this.lambdaQuery()
                .eq(Candidate::getTaskId,taskId)
                .eq(Candidate::getId,candidateId)
                .eq(Candidate::getDelFlag,false)
                .last("limit 1")
                .one();
    }

    @Override
    public BucketVO getBucketInfo(String taskId, String officeId) {
        ApiResponse<Bucket> response = tpsClient.getTencentCosTempCredential(officeId);
        if (response.getCode() != 1) {
            throw new KqException(ResponseCode.FAIL_TO_GET_BUCKET_INFO.getCode(),
                    ResponseCode.FAIL_TO_GET_BUCKET_INFO.getMsg());
        }
        Task task = iTaskService.getById(taskId);
        if (ObjectUtils.isEmpty(task)) {
            throw new KqException(ResponseCode.TASK_NOT_FOUND.getCode(),
                    ResponseCode.TASK_NOT_FOUND.getMsg());
        }
        Bucket bucket = response.getData();
        BucketVO bucketVO = new BucketVO();
        BeanUtils.copyProperties(bucket, bucketVO);
        String targetPath = TencentCloudCosUtil.getPhotoRootPath(task.getId());
        bucketVO.setPath(targetPath);
        return bucketVO;
    }

    @Override
    public boolean hasCandidate(String taskId, String candidateId) {
        return this.lambdaQuery().select(Candidate::getId)
                .eq(Candidate::getTaskId, taskId)
                .eq(Candidate::getId, candidateId)
                .eq(Candidate::getDelFlag, false)
                .last("LIMIT 1")
                .oneOpt()
                .isPresent();
    }

    @Override
    public List<CandidateInfoDTO> getCandidateInfoDTO(String taskId, Boolean proceed) {
        //获取考生准考证数据
        List<CandidateInfoDTO> candidateInfoList = baseMapper.getCandidateInfoDTO(taskId, proceed);
        //处理考生准考证数据
        candidateInfoList.forEach(candidateInfoDTO -> {
            //获取批次数据
            List<TimeInfoDTO> timeInfoDTOList = candidateInfoDTO.getTimeInfoDTOList();
            //批次数据按照批次编号排序
            timeInfoDTOList = timeInfoDTOList.stream()
                    .sorted(Comparator.comparing(TimeInfoDTO::getTimeCode))
                    .collect(Collectors.toList());
            //拼接多个考点名称
            String placeNames = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getPlaceName)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考点地址
            String placeAddresses = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getPlaceAddress)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考场地址
            String roomAddresses = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getRoomAddress)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考试id
            String examIds = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getExamId)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            candidateInfoDTO.setPlaceNames(placeNames)
                    .setPlaceAddresses(placeAddresses)
                    .setRoomAddresses(roomAddresses)
                    .setExamIds(examIds)
                    .setTimeInfoDTOList(timeInfoDTOList);
        });
        return candidateInfoList;
    }

    @Override
    public List<CandidateInfoDTO> getCandidateInfoDTOByCandidateIds(String taskId, Set<String> candidateIds) {
        //获取考生准考证数据
        List<CandidateInfoDTO> candidateInfoList = baseMapper.getCandidateInfoDTOByCandidateIds(taskId, candidateIds);
        //处理考生准考证数据
        candidateInfoList.forEach(candidateInfoDTO -> {
            //获取批次数据
            List<TimeInfoDTO> timeInfoDTOList = candidateInfoDTO.getTimeInfoDTOList();
            //批次数据按照批次编号排序
            timeInfoDTOList = timeInfoDTOList.stream()
                    .sorted(Comparator.comparing(TimeInfoDTO::getTimeCode))
                    .collect(Collectors.toList());
            //拼接多个考点名称
            String placeNames = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getPlaceName)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考点地址
            String placeAddresses = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getPlaceAddress)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考场地址
            String roomAddresses = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getRoomAddress)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            //拼接多个考试id
            String examIds = timeInfoDTOList.stream()
                    .map(TimeInfoDTO::getExamId)
                    .filter(StringUtils::isNotBlank)
                    .distinct()
                    .collect(Collectors.joining(","));
            candidateInfoDTO.setPlaceNames(placeNames)
                    .setPlaceAddresses(placeAddresses)
                    .setRoomAddresses(roomAddresses)
                    .setExamIds(examIds)
                    .setTimeInfoDTOList(timeInfoDTOList);
        });
        return candidateInfoList;
    }

    /**
     * 读取excel文件
     * @param file
     * @param taskId
     */
    @Override
    public TaskCandidateListener readExcel(MultipartFile file, String taskId) throws Exception {
        TaskCandidateListener listener =  new TaskCandidateListener(taskId);
        EasyExcel.read(file.getInputStream())
                .head(ImportTaskCandidateSheet.class)
                .registerReadListener(listener)
                .sheet(2)
                .doReadSync();
        return listener;
    }

    /**
     * 校验上传的文件
     * @param file
     */
    private void validFile(File file) {
        //文件大小限制值
        float fileMaxSize = configManager.getCandidateImportMaxSize();
        float fileMinSize = configManager.getCandidateImportMinSize();
        String fileSuffix = configManager.getCandidateImportFormat();
        //校验图片文件是否存在
        if(null == file || !file.exists()){
            //.xlsx文件为空
            //throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_FILE_IS_NULL.getCode(),ResponseCode.ERROR_BATCH_IMPORT_FILE_IS_NULL.getMsg());
            BusinessLogicException.throwException(43051403, "上传的文件不能为空");
        }
        //获取文件名
        String filename = file.getName();
        if(StringUtils.isNotEmpty(filename)) {
        	//获取文件后缀名
            String suffix = filename.substring(filename.lastIndexOf("."));
            //校验文件是否为 .xlsx文件
            if(!fileSuffix.equalsIgnoreCase(suffix)){
                //throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_INVALID_XLSX.getCode(), ResponseCode.ERROR_BATCH_IMPORT_INVALID_XLSX.getMsg());
                BusinessLogicException.throwException(43051403, "上传的文件类型不正确");
            }
            //校验.xlsx文件大小
            /*Long size = file.getTotalSpace();
            if (size.compareTo(fileMinSize) < 0 || size.compareTo(fileMaxSize) > 0) {
                //.xlsx文件大小不在规定范围内
                throw new KqException(ResponseCode.ERROR_BATCH_IMPORT_VOLUME_ERROR.getCode(), ".xlsx文件大小不能超过" + fileMaxSize + "kb");
            }*/
        }
    }

    /**
     * 获取考生信息，根据报名考生id，多个考生id逗号分隔。
     * @param candidateBmIds
     * @return
     */
    @Override
    public List<Candidate> getCandidateByCandidateBmIds(String candidateBmIds) {
    	if(StringUtils.isNotEmpty(candidateBmIds)) {
    		ArrayList<String> candidateBmIdList = new ArrayList<String>(Arrays.asList(candidateBmIds.split(","))) ;
    		return baseMapper.selectCandidateByCandidateBmIds(candidateBmIdList);
    	}
    	return new ArrayList<>();
    }

	@Override
	public List<CandidateCountDTO> getTaskCandidateCountByTaskids(List<String> taskIds) {
		List<CandidateCountDTO> candidateCountDTOs = new ArrayList<>();
		if (CollectionUtils.isEmpty(taskIds)) {
			return candidateCountDTOs;
		}
		Map<String, Integer> taskCountMap = new HashMap<>();
		// 获取数量
		List<Map<String, Object>> candidateCount = candidateMapper.getTaskCandidateCountByTaskids(taskIds);
		if(!CollectionUtils.isEmpty(candidateCount)) {
			for (Map<String, Object> map : candidateCount) {
				String taskid = String.valueOf(map.get("taskId"));
				Integer count = Integer.valueOf(String.valueOf(map.get("count")));
				taskCountMap.put(taskid, count);
				CandidateCountDTO candidateCountDTO = new CandidateCountDTO();
				candidateCountDTO.setTaskId(taskid);
				candidateCountDTO.setCandidateCount(count);
				candidateCountDTOs.add(candidateCountDTO);
			}
		}
		for (String taskId : taskIds) {
			if(null == taskCountMap.get(taskId)) {
				CandidateCountDTO candidateCountDTO = new CandidateCountDTO();
				candidateCountDTO.setTaskId(taskId);
				candidateCountDTO.setCandidateCount(0);
				candidateCountDTOs.add(candidateCountDTO);
			}
		}
		return candidateCountDTOs;
	}

    /**
     * 从平台导入机构内部考生
     * @param param
     */
    @Override
    public void batchImportFromPt(PtCandidateImportParam param){
        String taskId = param.getTaskId();
        //考试任务id不能为 空
        if (StringUtils.isBlank(taskId)) {
            throw new KqException(ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_TASK_ID_NULL.getCode(),
                    ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_TASK_ID_NULL.getMsg());
        }
        Task task = iTaskService.getTaskById(taskId);
        //: 校验任务数据锁定状态(后续版本考虑优化为从缓存中获取状态)
        iTaskService.isLockDataThrowException(taskId);
        //如果任务绑定报名，则不允许添加考生
        iTaskService.checkBindBm(taskId);
        //从平台拉取考生
        List<String> candidateIds = param.getCandidateIds();
        List<String> deptIds = param.getDeptIds();
        if(CollectionUtils.isEmpty(param.getCandidateIds()) && CollectionUtils.isEmpty(deptIds)){
            return;
        }
        List<PtCandidateDTO> fromPt = getFromPt(candidateIds,deptIds);
        List<PtCandidateDTO> repeatIdCard = isRepeatIdCard(fromPt, taskId);

        //任务报名字段默认为0，表示导入模式
        if (StringUtils.isBlank(task.getTaskBmId())) {
            iTaskService.updateTaskBmId(taskId,String.valueOf(CodeEnum.TASK_DATA_IMPORT_PT_VALUE.getCode()));
        }

        //保存考生照片数据
        for(PtCandidateDTO candidateParam : repeatIdCard){
            //处理id
            String id = UUIDUtils.newSortUUID();
            candidateParam.setPtCandidateId(candidateParam.getId());
            candidateParam.setId(id);
            //直接使用用户id作为考生id
            String candidateId = candidateParam.getId();
            List<CandidatePhoto> photos = new ArrayList<>(fromPt.size() * 3);
            //用户上传了考生照片则插入考生照片表，反之则不插入
            if(StringUtils.isNotBlank(candidateParam.getIdcardFront())){
                CandidatePhoto candidatePhoto = new CandidatePhoto();
                candidatePhoto.setCandidateId(candidateId)
                        .setPhotoType(PhotoTypeEnum.ID_CARD_FRONT.getCode())
                        .setPhotoPath(candidateParam.getIdcardFront())
                        .setName(PhotoTypeEnum.ID_CARD_FRONT.getRemark())
                        .setTaskId(taskId);
                photos.add(candidatePhoto);
            }
            if(StringUtils.isNotBlank(candidateParam.getIdcardBack())){
                CandidatePhoto candidatePhoto = new CandidatePhoto();
                candidatePhoto.setCandidateId(candidateId)
                        .setPhotoType(PhotoTypeEnum.ID_CARD_BACK.getCode())
                        .setPhotoPath(candidateParam.getIdcardBack())
                        .setName(PhotoTypeEnum.ID_CARD_BACK.getRemark())
                        .setTaskId(taskId);
                photos.add(candidatePhoto);
            }
            if(StringUtils.isNotBlank(candidateParam.getCertificatePhoto())){
                CandidatePhoto candidatePhoto = new CandidatePhoto();
                candidatePhoto.setCandidateId(candidateId)
                        .setPhotoType(PhotoTypeEnum.PERSONAL.getCode())
                        .setPhotoPath(candidateParam.getCertificatePhoto())
                        .setName(PhotoTypeEnum.PERSONAL.getRemark())
                        .setTaskId(taskId);
                photos.add(candidatePhoto);
            }
            if(!CollectionUtils.isEmpty(photos)){
                //插入考生照片表
                if (!iCandidatePhotoService.saveBatch(photos)) {
                    throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getCode(),
                            ResponseCode.ERROR_ADD_CANDIDATE_INSERT_PHOTO_FAIL.getMsg());
                }
            }
        }
        //插入考生表
        List<Candidate> candidates = new ArrayList<>(fromPt.size());
        for(PtCandidateDTO candidateParam : fromPt){
            Candidate candidate = new Candidate();
            candidate.setTaskId(taskId);
            candidate.setImportType(1);
            BeanUtils.copyProperties(candidateParam,candidate);
            candidates.add(candidate);
            candidate.setId(candidateParam.getId());
            candidate.setBmStudentId(candidateParam.getPtCandidateId());
            candidate.setUserId(candidateParam.getUserId());
            candidate.setIdType(NumberUtils.toInt(candidateParam.getIdType()));
            candidate.setSex(NumberUtils.toInt(candidateParam.getSex()));
        }
        if(!CollectionUtils.isEmpty(candidates)){
            if (!saveBatch(candidates)) {
                //插入考生表失败
                throw new KqException(ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getCode(),
                        ResponseCode.ERROR_ADD_CANDIDATE_INSERT_FAIL.getMsg());
            }
        }
    }

    //证件号码是否重复
    private List<PtCandidateDTO> isRepeatIdCard(List<PtCandidateDTO> candidates,String taskId){
        /*StringBuilder idcard = new StringBuilder();
        for(PtCandidateDTO candidateParam : candidates){
            Candidate findByIdNumber = iCandidateService.lambdaQuery()
                    .eq(Candidate::getIdNumber, candidateParam.getIdNumber())
                    .eq(Candidate::getDelFlag, false)
                    .eq(Candidate::getTaskId, taskId).last("limit 1")
                    .one();
            if(Objects.nonNull(findByIdNumber)){
                idcard.append(findByIdNumber.getIdNumber()).append("  ");
            }
        }
        if(StringUtils.isNotBlank(idcard.toString())){
            throw new KqException(ResponseCode.ERROR_UPDATE_CANDIDATE_ID_NUMBER_EXISTENT.getCode(),
                    idcard.toString() + "  以上证件号码重复");
        }*/
        //去库里查一下哪些是重复的去掉
        Iterator<PtCandidateDTO> iterator = candidates.iterator();
        while (iterator.hasNext()){
            PtCandidateDTO next = iterator.next();
            LambdaQueryWrapper<Candidate> query = Wrappers.lambdaQuery();
            query.eq(Candidate::getBmStudentId,next.getId()).eq(Candidate::getDelFlag,Boolean.FALSE).eq(Candidate::getTaskId,taskId);
            int count = this.count(query);
            if(count > 0){
                //重复了
                iterator.remove();
            }
        }
        return candidates;
    }

    private List<PtCandidateDTO> getFromPt(List<String> candidateIds,List<String> deptIds){
        //总的考生数据
        List<PtCandidateDTO> ptCandidates = new ArrayList<>();
        Integer count = 0;
        Integer pageSize = 500;
        //去平台拿总数据
        if(!CollectionUtils.isEmpty(deptIds)){
            ApiResponse<Page<PtCandidateDTO>> byPage = govPtClient.getByPage(1, 1,deptIds,null);
            if(Objects.isNull(byPage) || !Objects.equals(ResponseCode.SUCCESS.getCode(),byPage.getCode()) ||
                    Objects.isNull(byPage.getData())){
                throw new KqException(ResponseCode.GET_CANDIATE_FROM_PT_ERROR);
            }
            count = (int) byPage.getData().getTotal();
            Integer pageTotal = 0;//总的次数
            if (count % pageSize == 0) {
                pageTotal = count / pageSize;
            } else {
                pageTotal = count / pageSize + 1;
            }
            for(int i = 1 ; i <= pageTotal; i++ ){
                ApiResponse<Page<PtCandidateDTO>> byPage2 = govPtClient.getByPage(pageSize,i,deptIds,null);
                if(Objects.isNull(byPage2) || !Objects.equals(ResponseCode.SUCCESS.getCode(),byPage2.getCode()) ||
                        Objects.isNull(byPage2.getData())){
                    throw new KqException(ResponseCode.GET_CANDIATE_FROM_PT_ERROR);
                }
                List<PtCandidateDTO> list = byPage2.getData().getRecords();
                ptCandidates.addAll(list);
            }
            return ptCandidates;
        } else if(!CollectionUtils.isEmpty(candidateIds)){
            count = candidateIds.size();
            Integer pageTotal = 0;//总的次数
            if (count % pageSize == 0) {
                pageTotal = count / pageSize;
            } else {
                pageTotal = count / pageSize + 1;
            }
            for(int i = 1 ; i <= pageTotal; i++ ){
                int startIndex = (i -1) * pageSize;
                int endIndex = i * pageSize;
                if(endIndex >= count){
                    endIndex = count;
                }
                List<String> strings = candidateIds.subList(startIndex, endIndex);

                ApiResponse<Page<PtCandidateDTO>> byPage = govPtClient.getByPage(pageSize,1,null,strings);
                if(Objects.isNull(byPage) || !Objects.equals(ResponseCode.SUCCESS.getCode(),byPage.getCode()) ||
                        Objects.isNull(byPage.getData())){
                    throw new KqException(ResponseCode.GET_CANDIATE_FROM_PT_ERROR);
                }
                ptCandidates.addAll(byPage.getData().getRecords());
            }
            return ptCandidates;
        }
        return ptCandidates;
    }

    /**
     * 查询考试任务选择的考生
     * @param taskId
     * @return
     */
    @Override
    public List<String> queryTaskCandiate(String taskId){
        LambdaQueryWrapper<Candidate> query = Wrappers.lambdaQuery();
        query.eq(Candidate::getTaskId,taskId).eq(Candidate::getDelFlag,Boolean.FALSE);
        query.select(Candidate::getId,Candidate::getBmStudentId);
        List<Candidate> candidates = this.baseMapper.selectList(query);
        List<String> candidateIds = candidates.stream().filter(c -> Objects.nonNull(c.getBmStudentId()))
                .map(Candidate::getBmStudentId).collect(Collectors.toList());
        return candidateIds;
    }

    @SneakyThrows
    @Override
    public void exportExcel(String taskId) {
        // 文件名称
        String lastFileName = UUIDUtils.randomUUID() + ".xlsx";
        File tempFile = FileUtil.createTempFile(lastFileName, Boolean.TRUE);
        tempFile.deleteOnExit();
        if(TextUtil.isNull(taskId)){
            throw new KqException(ResponseCode.ERROR_EXPORT_CANDIDATE_TEMPLATE_TASK_ID_NULL.getCode(),"任务ID不能为空！");
        }else{

            try{
                Map<String, String> deptMap = new HashMap<>();
                ProgressManager.updateProgressCurrent(10L, "数据准备中");
                Map<String, TaskCandidateArea> taskAreaMap = iTaskCandidateAreaService.lambdaQuery().eq(TaskCandidateArea::getTaskId, taskId).eq(BaseEntity::getDelFlag, false)
                        .list().stream().collect(Collectors.toMap(TaskCandidateArea::getAreaId, Function.identity(),(k1,k2) -> k1));
                LambdaQueryWrapper<Candidate> query = Wrappers.lambdaQuery();
                query.eq(Candidate::getTaskId,taskId).eq(Candidate::getDelFlag,Boolean.FALSE);

                List<Candidate> candidates = this.baseMapper.selectList(query);

                List<ExportTaskCandidateSheet> sheets = new ArrayList<>(candidates.size());
                ProgressManager.updateProgressCurrent(15L, "数据准备中");
                //查考生科目信息
                List<CandidateSubjectExportDTO> candidateSubjectExportDTOS = candidateSubjectMapper.selectForCandidateExport(taskId);

                Map<String, CandidateSubjectExportDTO> answerTypeMap = new HashMap<>();

                if(!CollectionUtils.isEmpty(candidateSubjectExportDTOS)){
                    answerTypeMap = candidateSubjectExportDTOS.stream().collect(Collectors.toMap(CandidateSubjectExportDTO::getCandidateId, Function.identity()));
                }
                ProgressManager.updateProgressCurrent(22L, "数据准备中");

                //查考生的准考证信息
                LambdaQueryWrapper<CustomArrange> csQuery = Wrappers.lambdaQuery();
                csQuery.eq(CustomArrange::getTaskId,taskId).eq(CustomArrange::getDelFlag,Boolean.FALSE);
                csQuery.select(CustomArrange::getCandidateId,CustomArrange::getAdmissionNumber);

                // 读取自定准考证
                List<CustomArrange> list = iCustomArrangeService.list(csQuery);
                Map<String,String> admissionMap = list.stream().filter(a -> StringUtils.isNotBlank(a.getCandidateId()) && StringUtils.isNotBlank(a.getAdmissionNumber())).
                        collect(Collectors.toMap(CustomArrange::getCandidateId,
                        CustomArrange::getAdmissionNumber,(k1,k2) -> k1));
                ProgressManager.updateProgressCurrent(33L, "数据准备中");

                List<Candidate> deptAll = candidates.stream().filter(distinctByKey(Candidate::getDeptId)).collect(Collectors.toList());

                List<String> deptIds = deptAll.stream().filter(s -> StringUtils.isNotBlank(s.getDeptId()))
                        .map(Candidate::getDeptId).collect(Collectors.toList());
                Map<String, String> ancestorsNames = new HashMap<>();
                if(deptIds.size() > 0) {
                    ancestorsNames.putAll(iUaDeptService.getAncestorsNames(deptIds));
                }

                for(Candidate candidate : candidates){
                    ExportTaskCandidateSheet sheet = new ExportTaskCandidateSheet();
                    sheets.add(sheet);
                    sheet.setName(candidate.getName());
                    sheet.setBirthday(candidate.getBirthday());
                    sheet.setEmail(candidate.getEmail());
                    sheet.setSex(SexEnums.getDescByType(candidate.getSex()));
                    sheet.setIdType(IdentityTypeEnum.getDescByType(candidate.getIdType()));
                    sheet.setIdNumber(candidate.getIdNumber());
                    sheet.setSubject(answerTypeMap.get(candidate.getId()).getName());
                    sheet.setAnswerType(AnswerTypeEnum.getDescByType(answerTypeMap.get(candidate.getId()).getAnswerType()));
                    sheet.setPhone(candidate.getPhone());

                    TaskCandidateArea taskCandidateArea = taskAreaMap.get(candidate.getDistrict());
                    if(taskCandidateArea != null) {
                        sheet.setMinAreaLevel(taskCandidateArea.getFullName());
                    }

                    if(StringUtils.isNotBlank(candidate.getDeptId())) {
                        sheet.setDeptId(ancestorsNames.get(candidate.getDeptId()));
                    }

                    sheet.setAdmissionNumber(admissionMap.get(candidate.getId()));
                }
                ProgressManager.updateProgressCurrent(42L, "数据准备中");

                ProgressManager.updateProgressCurrent(55, "数据准备中");
                //考生
                EasyExcel.write(tempFile, ExportTaskCandidateSheet.class).sheet("考生信息").doWrite(sheets);

                String path = "kw-gov/candidate/" + lastFileName;
                ProgressManager.updateProgressCurrent(81L, "文件开始生成");
                String fileUrl = cloudObsService.updateFile(path, tempFile);
                log.info("# excel文件上传华为云成功：{}，{}", lastFileName, fileUrl);
                ProgressManager.updateProgressCurrent(100L, fileUrl);

            }catch (Exception e){
                log.error("导出考生信息失败，考试任务id ：【{}】",taskId,e);
                throw new KqException(ResponseCode.ERROR_EXPORT_CANDIDATE.getCode(),ResponseCode.ERROR_EXPORT_CANDIDATE.getMsg());
            } finally {
                // 上面已经加了钩子，这里不需要手动删除
               // tempFile.delete();
            }
        }
    }

    private String changeArea(Candidate candidate){
        if(StringUtils.isNotBlank(candidate.getDistrict())){
            return candidate.getDistrict();
        }
        if(StringUtils.isNotBlank(candidate.getCity())){
            candidate.getCity();
        }
        return candidate.getProvince();
    }

    /**
     * 获取任务考生区域信息
     * @return
     */
    public List<OfficeAreaSheet> getOfficeAreaSheet(String taskId){
        List<OfficeAreaSheet> officeAreaSheets = new ArrayList<>();
        // 二级
        List<TaskCandidateArea> districtList = new ArrayList<>();
        // 三级
        Map<String, TaskCandidateArea> dictionaryMap = new HashMap<>(3);
        LambdaQueryWrapper<TaskCandidateArea> areaQuery = Wrappers.lambdaQuery();
        areaQuery.eq(TaskCandidateArea::getTaskId,taskId).eq(TaskCandidateArea::getDelFlag,Boolean.FALSE);
        List<TaskCandidateArea> allAreas = iTaskCandidateAreaService.list(areaQuery);
        //判断一下是否没有自己定义区域信息
        Boolean isSelf = Boolean.TRUE;
        List<String> allIds = TaskCandidateAreaVirtualAreaIdEnums.getAllIds();
        if(allAreas.size() == 4){
            for(TaskCandidateArea area : allAreas){
                if(!allIds.contains(area.getAreaId())){
                    isSelf = Boolean.FALSE;
                    break;
                }
            }
        } else {
            isSelf = Boolean.FALSE;
        }

        if(!isSelf){
            // 得到字典,和县级的数据
            for (TaskCandidateArea taskArea : allAreas){
                if("4".equals(taskArea.getType())) {
                    districtList.add(taskArea);
                }
                dictionaryMap.put(taskArea.getAreaId(), taskArea);
            }
            // 组装数据
            for(TaskCandidateArea taskArea : districtList){
                OfficeAreaSheet officeAreaSheet = new OfficeAreaSheet();
                officeAreaSheet.setDistrictCode(taskArea.getAreaId());
                officeAreaSheet.setDistrictName(taskArea.getName());
                TaskCandidateArea cityData = dictionaryMap.get(taskArea.getParentId());
                if(null != cityData){
                    officeAreaSheet.setCityCode(cityData.getAreaId());
                    officeAreaSheet.setCityName(cityData.getName());
                    TaskCandidateArea provinceData = dictionaryMap.get(cityData.getParentId());
                    if(null != provinceData) {
                        officeAreaSheet.setProvinceCode(provinceData.getAreaId());
                        officeAreaSheet.setProvinceName(provinceData.getName());
                    }
                }
                officeAreaSheets.add(officeAreaSheet);
            }
            // 返回数据
            return officeAreaSheets;
        }
        List<TaskArea> allAreaList = iOfficeAreaService.getAllAreaList();
        // 二级
        List<TaskArea> districtList2 = new ArrayList<>();
        // 三级
        Map<String, TaskArea> dictionaryMap2 = new HashMap<>(4500);
        // 得到字典,和县级的数据
        for (TaskArea taskArea : allAreaList){
            Integer level = 3;
            if(level.equals(taskArea.getRegionLevel())) {
                districtList2.add(taskArea);
            }
            dictionaryMap2.put(taskArea.getAreaId(), taskArea);
        }
        // 组装数据
        for(TaskArea taskArea : districtList2){
            OfficeAreaSheet officeAreaSheet = new OfficeAreaSheet();
            officeAreaSheet.setDistrictCode(taskArea.getAreaId());
            officeAreaSheet.setDistrictName(taskArea.getName());
            TaskArea cityData = dictionaryMap2.get(taskArea.getParentId());
            if(null != cityData){
                officeAreaSheet.setCityCode(cityData.getAreaId());
                officeAreaSheet.setCityName(cityData.getName());
                TaskArea provinceData = dictionaryMap2.get(cityData.getParentId());
                if(null != provinceData) {
                    officeAreaSheet.setProvinceCode(provinceData.getAreaId());
                    officeAreaSheet.setProvinceName(provinceData.getName());
                }
            }
            officeAreaSheets.add(officeAreaSheet);
        }
        // 返回数据
        return officeAreaSheets;
    }

    //导入考生时处理数据区域数据
    private List<OfficeArea> getOfficeArea(List<OfficePlace> officePlaceList, String officeId) {
        //根据考点数据获取机构区域编码
        List<String> areaIdList = new ArrayList<>();
        for (OfficePlace place : officePlaceList) {
            areaIdList.add(place.getProvince());
            areaIdList.add(place.getCity());
            areaIdList.add(place.getDistrict());
        }
        areaIdList = areaIdList.stream()
                .distinct()
                .collect(Collectors.toList());
        //根据机构区域编码获取机构区域模板数据
        //return officeAreaMapper.getByAreaIdList(areaIdList, officeId);
        ApiResponse<Object> areaRes = govPtClient.getAreaByIds(areaIdList);
        if(!Objects.equals(20000,areaRes.getCode()) || Objects.isNull(areaRes.getData())){
            throw new GovInvalidRequestException(ResponseCode.ROOM_ADD_PT_AREA_PAGE.getCode(),
                    ResponseCode.ROOM_ADD_PT_AREA_PAGE.getMsg());
        }
        List<AreaDTO> dtos = JSON.parseArray(JSON.toJSONString(areaRes.getData()),AreaDTO.class);
        List<OfficeArea> areas = new ArrayList<>(dtos.size());
        for(AreaDTO dto : dtos){
            OfficeArea area = new OfficeArea();
            area.setAreaId(dto.getId());
            area.setName(dto.getName());
            area.setRemarks(dto.getRemarks());
            area.setSort(dto.getSort());
            area.setType(String.valueOf(dto.getRegionLevel()));
            area.setParentId(String.valueOf(dto.getParentId()));
            area.setParentIds(dto.getParentIds());
            areas.add(area);
        }
        return areas;
    }

    //处理当前选择的从平台选择的考生带的区域信息
    private List<TaskCandidateArea> collectSelectedCandidateAreaList(List<OfficeArea> officeAreaList,String taskId){
        //根据考试任务id获取当前任务编排考场数据
        QueryWrapper<TaskCandidateArea> taskAreaQueryWrapper = new QueryWrapper<>();
        taskAreaQueryWrapper.lambda()
                .eq(TaskCandidateArea::getTaskId, taskId)
                .eq(TaskCandidateArea::getDelFlag, false);
        List<TaskCandidateArea> currentTaskAreaList = iTaskCandidateAreaService.list(taskAreaQueryWrapper);
        if (!CollectionUtils.isEmpty(currentTaskAreaList)) {
            //去掉重复的考试任务编排区域数据，留下新增的考试任务编排区域数据
            List<String> taskAreaIdList = currentTaskAreaList.stream()
                    .map(TaskCandidateArea::getAreaId)
                    .collect(Collectors.toList());
            officeAreaList = officeAreaList.stream()
                    .filter(officeArea -> taskAreaIdList.stream()
                            .noneMatch(taskAreaId -> taskAreaId.equals(officeArea.getAreaId())))
                    .collect(Collectors.toList());
        }
        //生成考试任务编排区域数据
        List<TaskCandidateArea> taskAreaList = new ArrayList<>();
        officeAreaList.forEach(officeArea -> {
            TaskCandidateArea taskArea = new TaskCandidateArea();
            BeanUtils.copyProperties(officeArea, taskArea);
            taskArea.setTaskId(taskId).setId(UUIDUtils.newSortUUID());
            taskAreaList.add(taskArea);
        });
        //如果有值了，则把自定义的清理掉
        LambdaUpdateWrapper<TaskCandidateArea> update = Wrappers.lambdaUpdate();
        update.set(TaskCandidateArea::getDelFlag,Boolean.FALSE);
        update.in(TaskCandidateArea::getAreaId, TaskCandidateAreaVirtualAreaIdEnums.getAllIds());
        iTaskCandidateAreaService.update(update);
        return taskAreaList;
    }

    //用与判断区域是否选择完整
    private void checkArea(CandidateParam param){
        String provice = param.getProvince();
        String city = param.getCity();
        String district = param.getDistrict();

        if(StrUtil.isBlankIfStr(provice) && StrUtil.isBlankIfStr(city) && StrUtil.isBlankIfStr(district)){
            return;
        }
        if(!StrUtil.isBlankIfStr(provice) && !StrUtil.isBlankIfStr(city) && !StrUtil.isBlankIfStr(district)){
            return;
        }
        throw new KqException(11135001,"如勾选了区域信息则省市区需行勾选完整");
    }
}
